1、Java中两种数据类型(为后面进一步提问做铺垫)
(1)基本数据类型,分为boolean、byte、int、char、long、short、double、float;
(2)引用数据类型 ,分为数组、类、接口。
扩展:Java中引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
基本数据类型: boolean,char,byte,short,int,long,float,double
封装类类型: Boolean,Character,Byte,Short,Integer,Long,Float,Double
2、java中==和eqauls()的区别
==分两类分析,既可以比较基本类型也可以比较引用类型,对于基本类型来说是比较的数值。对于引用类型来说比较的内存地址值;equals是属于java.lang.Object类里面的方法,Object里的equals里默认的是双等于==。分两种情况讨论:一种是自定义类,看自定义类有没有重写equals方法,通常情况下,如果重写了equals则比较的是类中相应属性是否相等。如果没有重写equals方法,则仍然使用==比较的是地址。
总结、:
== :
基本类型:比较值是否相等
引用类型:比较的就是内存地址是否相同
equals :
引用类型:默认情况下,比较的是地址值。可以进行重写,使其比较对象的值是否相等。
3、说说int和Integer有何区别
(1)Integer是int的包装类;int是基本数据类型;
(2)Integer变量必须实例化后才能使用;int变量不需要;
(3)Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
(4)Integer的默认值是null;int的默认值是0。
4、switch中能否使用string类型的参数作为变量(涉及到jdk版本的理解)
在JDK1.7之前,switch只能支持byte、short、char、int、float、double或者其对应的封装类以及Enum类型。JDK1.7开始支持String。当字符串不会频繁改变时可以用枚举来代替String。
5、说明ArrayList和LinkedList的区别和优缺点,在哪些场景会使用?
区别:
(1)ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表结构;
(2)对于随机访问的get和set方法,ArrayList要优于LinkedList,因为LinkedList要移动指针;
(3)对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据;
各自优缺点:
(1)对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是 统一的,分配一个内部Entry对象。
(2)在ArrayList集合中添加或者删除一个元素时,当前的列表移动元素后面所有的元素都会被移动。而LinkedList集合中添加或者删除一个元素的开销是固定的。
(3)LinkedList集合不支持 高效的随机随机访问(RandomAccess),因为可能产生二次项的行为。
(4)ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间。
应用场景:
ArrayList使用在查询比较多,但是插入和删除比较少的情况,而LinkedList用在查询比较少而插入删除比较多的情况
6、实现多线程的几种方式,多线程的应用场景有哪些?
(1)、继承Thread类,重写run方法
(2)、实现Runnable接口,重写run方法。【可以避免由于Java的单继承特性而带来的局限。适合多个线程去处理同一资源的情况】
(3)、实现Callable接口,重写call方法。【有返回值,允许抛出异常】
(4)、使用线程池【减少创建新线程的时间,重复利用线程池中线程,降低资源消耗,可有返回值】
7、${}和#{}的区别
使用#{}:
(1)、传入参数,sql在解析的时候会加上" ",当成字符串来解析,如 id = "id";
(2)、#{}能够很大程度上防止sql注入;
使用${}:
(1)、传入数据直接显示在生成的sql中,sql在解析的时候值为id = id。
(2)、${}方式无法防止sql注入
最后:能用#{}时尽量用#{},但有些场合需要使用$。
注意MyBatis排序时使用order by 动态参数时需要注意,用$而不是#(#会自动拼接符号)
8、描述一下JVM加载class文件的原理机制和特点。
Java中的所有类,都需要由类加载器装载到JVM中才能运行。JVM中类的加载是由类加载器(ClassLoader)和它的子类来实现的。在我们使用一个类之前,JVM需要先将该类的字节码文件(.class文件)从磁盘、网络或其他来源加载到内存中(加载Class文件到JVM),并对字节码进行解析生成对应的Class对象,这就是类加载器的功能。我们可以利用类加载器,实现类的动态加载。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
进一步提问:类加载的方式有几种?区别是什么?
类装载方式有两类:隐式装载和显示装载,其中显示装载又分2种方式。
(1)、隐式装载,程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,比如
Dog dog = new Dog();【第一种方式】
(2)、显式装载,
【第二种方式】使用Class.forName()通过反射加载类型,并创建对象实例,比如
Class clazz = Class.forName(“Dog”);
Object dog =clazz.newInstance();
如果无法找到Dog,则抛出ClassNotFoundException。
【第三种方式】使用某个ClassLoader实例的loadClass()方法
Class clazz = classLoader.loadClass("Dog");
Object dog=clazz.newInstance();
如果无法找到Dog,则抛出ClassNotFoundException。
区别:
方式1和2使用的类加载器是相同的,都是当前类加载器(即:this.getClass.getClassLoader)。
方式3由用户指定类加载器。如果需要在当前类路径以外寻找类,则只能采用方式3。即第3种方式加载的类与当前类分属不同的命名空间。
方式1是静态加载,方式2和3是动态加载。
进一步提问:java内置的类加载器(ClassLoader)有哪些,简述一下类加载器工作原理
Java的类加载器有三个:
第一种是Bootstrap Loader(引导类加载器)。它的实现依赖于底层操作系统,由C编写而成,没有继承于ClassLoader类。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。默认为jre目录下的lib目录下的class文件,该加载器没有父加载器。负责加载虚拟机的核心类库,如java.lang.*。Object类就是由根类加载器加载的。
第二种是Extended Loader(标准扩展类加载器)。它的父加载器为根类加载器。由java编写而成,是ClassLoader的子类。它从java.ext.dirs中加载类库,或者从JDK安装目录jre\lib\ext子目录下加载类库。如果把用户创建的jar文件放在该目录下,也会自动由扩展类加载器加载。
第三种是AppClass Loader(应用程序类路径类加载器)。它的父加载器为扩展类加载器。由java编写而成,是ClassLoader的子类,它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类,是用户自定义的类加载器的默认父加载器。
加载类时,会以Bootstrap Loader→Extended Loader→AppClass Loader的顺序来寻找类,如果找不到,就会丢出NoClassDefFoundError。
9、在一个类中,声明了若干个static方法和非static方法,声明的static方法能否直接访问声明的非static方法?
static方法不能直接访问非static方法,因为static方法是属于这个类本身的一个方法,在编译期间就已经确定了;而非static方法是属于这个类的对象的方法,需要在实例化之后才能访问到。即:static方法调用时不需要创建对象,可以直接调用,非static方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,若在static方法中访问非static方法,非static方法不知道关联到哪个对象上,将不能通过编译。
进一步提问:说一下静态方法和非静态方法都是在什么时候被装载到内存中的?
静态方法(Static Method)与静态成员变量一样,属于类本身,在类装载的时候被装载到内存(Memory),不自动进行销毁,会一直存在于内存中,直到JVM关闭。
非静态方法(Non-Static Method)又叫实例化方法,属于实例对象,实例化后才会分配内存,必须通过类的实例来引用。不会常驻内存,当实例对象被JVM 回收之后,也跟着消失。
进一步提问:静态方法怎样访问非静态方法?
静态方法不能直接使用本类的非静态方法
解决方式有三种
(1)、两个方法都改成非静态
(2)、两个方法都改成静态
(3)、先创建类的实例,然后静态方法再调用这个实例的非静态方法
10、在Java中,对象什么时候可以被垃圾回收?
Java垃圾回收不是实时的,垃圾回收器的作用是查找和回收(清理)无用的对象。以便让JVM更有效地使用内存。垃圾回收器的运行时间是不确定的,由JVM决定,在运行时是间歇执行的。也可以通过System.gc()来强制回收垃圾,但是这个命令下达后JVM不一定会立即响应执行,但间隔一小段时间基本都会执行。
11、一个汉字占几个字节
中文在不同编码下占不定长的 2~4个字节。注意在utf-16中占用两个字节,在java 运行时用UTF-16编码在转码的时候会在前面加上表示字节顺序的字符,这个字符称为”零宽度非换行空格”(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。FEFF占用两个字节。
12、项目中使用Maven进行构建,有哪些优势?你使用过哪些maven命令?说一下maven中本地仓库和远程仓库的区别和联系。
优点:
(1)创建项目,自动关联和下载依赖的jar包,统一维护jar包
(2)升级框架版本方便
常用 Maven 命令:
(1)、安装项目到本地仓库:mvn install
(2)、创建maven项目:mvn archetype:generate
(3)、验证项目是否正确:mvn validate
(4)、maven 打包:mvn package
(5)、只打jar包:mvn jar:jar
(6)、生成源码jar包:mvn source:jar
(7)、产生应用需要的任何额外的源代码:mvn generate-sources
(8)、编译源代码: mvn compile
(9)、编译测试代码:mvn test-compile
(10)、运行测试:mvn test
(11)、运行检查:mvn verify
(12)、清理maven项目:mvn clean
Maven仓库关系:
maven的仓库只有两大类:1.本地仓库 2.远程仓库,在远程仓库中又分成了3种:2.1 中央仓库 2.2 私服 2.3 其它公共库
运行Maven的时候,Maven所需要的任何构件都是直接从本地仓库获取的。如果本地仓库没有,它会首先尝试从远程仓库下载构件至本地仓库,然后再使用本地仓库的构件。
13、Thread.sleep(0)有没有实际作用
触发操作系统立刻重新进行一次CPU竞争,操作系统重新计算线程的优先级(包括当前线程)。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。
1、在项目中使用过哪些前端框架?
Vue(用于构建用户界面的 渐进式框架,特点:轻量级、双向数据绑定、组件化)、
React(构建用户界面的 JAVASCRIPT 库,特点:只负责显示、声明式框架、数据驱动DOM)、
Angular(前端JS框架,核心:MVVM、模块化、自动化双向数据绑定、语义化标签、依赖注入等)、
QucikUI(企业级web前端开发解决方案)、
Layui(前端 UI 框架)、
Avalon(前端MVVM框架)、
还有Dojo、Ember、Aurelia等
2、js中如何查看某变量的数据类型?可以查看的数据类型有哪些?
使用typeof查看变量的数据类型。
javascript共有6种数据类型:
基本类型5种:number、string、boolean、null、undefined。引用类型1种:object
typeof检测返回6种:undefined、boolean、string、number、object、function
3、前端进行文件下载时,能不能用ajax向后端发起请求?
Ajax不能实现文件下载功能
原因:ajax的返回值是json,text,html,xml类型,或者可以说ajax的接收类型只能是String字符串,不是流类型,所以无法实现文件下载。但用aja仍然可以获得文件的内容(可以读取到返回的response,但只是读取而已),该文件将被保留在内存中,无法将文件保存到磁盘,这是因为javascript无法和磁盘进行交互,否则这会是一个严重的安全问题,js无法调用浏览器的下载处理机制和程序,会被浏览器阻塞。
4、如何解决前后端交互过程中特殊字符的传参(比如中文、特殊符号等)?
(这个问题属于送分题,有一定项目经验的人,都会注意到在前后端进行交互时,需要进行decoder编码-Encoder解码的过程,防止乱码)
进一步提问:前后端数组传参如何处理?
前端:数据使用JSON.stringify(str)处理
后端:数据转换:List<Object> objectList = JSONObject.parseArray(str, Object.class)
5、FreeMarker、jsp、html 三者的区别
先说说freemarker和jsp的不同,运行机制就不大一样,jsp是编译成继承自servlet的class文件,运行jsp就是运行一个servlet(Java文件编译后会产生一个class文件,最终执行的就是这个class文件,JSP也一样,它也要编译成class文件。JSP不止要编译,它还得要转译,首先把JSP转译成一个Servlet文件,然后再编译成class文件。当用户访问JSP时就执行了class文件)。
而freemarker就是套模板,通过模板+内容直接生成HTML然后输出。
HTML(Hypertext Markup Language)文本标记语言,它是静态页面,和JavaScript一样是解释性语言。
JSP(Java Server Page)Java服务端的页面,它是动态页面,它是需要经过JDK编译后把内容发给客户端去显示。
6、vue实例内部和外部分别怎么调用vue中的方法?
内部调用: this.operate();
外部调用:vm.operate(); (vm是vue实例名)
1、Spring:
(1)Spring中IOC和AOP的应用场景。
AOP:面向切面编程。可以运用在日志,事务和异常处理等。如果不使用aop,那么就必须在每个类和方法中去实现它们。代码纠缠在一起。每个类和方法中都包含日志、事务或者异常处理甚至是业务逻辑。在一个这样的方法中,很难分清代码中实际做的是什么处理。AOP 所做的就是将所有散落各处的事务代码集中到一个事务切面中。
AOP日志处理:使用Aop在接口方法上插入一行自定义的切面注解类,在切面处理类中可以记录接口名称、请求参数、请求ip、请求url、请求时间、响应参数、响应状态、调用时长等;
AOP事务处理:Spring在方法访问数据库之前,自动开启事务,当访问数据库结束之后,自动提交/回滚事务;
AOP异常处理:自定义开启环绕通知,一旦运行接口报错,环绕通知捕获异常跳转异常处理页面。
IOC就是Inversion of Control,即控制反转,又称依赖注入。它不是什么技术,而是一种设计思想。在Java开发中,传统的创建对象的方法是直接通过 new 关键字(之前我们通过 "类名 对象名 = new 类名( )"的方式进行对象的创建,也就是说我们的程序负责对象的创建,控制了它是否被创建这件事情,这就叫做控制),而 spring 则是通过 IOC 容器来创建对象,也就是说我们将创建对象的控制权交给了 IOC 容器。这称为控制反转。概括的说就是:
IOC 让程序员不再关注怎么去创建对象,而是关注于对象创建之后的操作,把对象的创建、初始化、销毁等工作交给spring容器来做。
举个例子:梳理这个问题在各种社会形态里如何解决:一个人(Java实例,调用者)需要一把斧子(Java实例,被调用者)
(1) 原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:Java程序里的调用者自己创建被调用者。
(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应Java程序的简单工厂的设计模式。
(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应Spring的依赖注入。
第一种情况下,Java实例的调用者创建被调用的Java实例,必然要求被调用的Java类出现在调用者的代码里。无法实现二者之间的松耦合。
第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。
第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,系统自动提供被调用者实例。事实上,调用者和被调用者都处于Spring的管理下,二者之间的依赖关系由Spring提供。
生活中这种例子比比皆是,支付宝在整个淘宝体系里就是庞大的ioc容器,交易双方之外的第三方资金管理中心。
(2)Spring依赖注入的方式有哪些?
【set设值注入、构造函数注入、spring注解注入】
Spring IOC既可以通过XML的形式进行bean与依赖注入配置,也可以通过注解的方式。(在springmvc中,我们一般使用xml进行装配,而springboot使用全注解的形式)
①通过XML的形式进行bean与依赖注入
通常有两种: 设值注入&构造注入。
设值注入就是指要被注入的类中定义有一个setter()方法,并在参数中定义需要注入的对象。
构造注入就是指要被注入的类中声明一个构造方法,并在此方法的参数中定义要注入的对象。
②注解的方式:
注解包含三部分:
| 组件类型注解--声明当前类的功能与职责
|| 自动装配注解--根据属性特征自动注入对象
||| 元数据注解--更细化的辅助IoC容器管理对象的注解
A、四种组件类型注解
@Component:组件注解,通用注解,该注解描述的类将被IoC容器管理并实例化
@Controller:语义注解,说明当前类是MVC应用中的控制类
@Service:语义注解,说明当前类是Service业务服务类
@Repository:语义注解,说明当前类作用于业务持久层,通常描述对应Dao类
此外,在使用四种组件类型的注解时,必须开启组件扫描,详细配置如下:
B、两类自动装配注解
按类型装配
@Autowired
@Inject
按名称装配
@Named
@Resource
优先设置name属性,若未包含name属性,会按照@Autowired注入
C、元数据注解
@Primary--按类型装配时出现多个相同类型的对象,拥有此注解对象优先被注入
@PostContruct:相当于init-method
@PreDestory:相当于destory--method
@Scope:设置对象Scope属性
@Value:为属性注入静态数据
(3)为什么非使用依赖注入,我要用到一个其他对象时,new一个怎么就不好了。
本质上都是创建对象,最大的区别还是生命周期的管理以及复杂依赖的处理。
①、生命周期
比如一个类或者接口全程只要一个实例,用依赖注入的话只需要注册成单例即可,如果自己实例化的话你需要撸一个单机模式(饿汉、懒汉、线程安全等模式)的类,并发下还要考虑线程安全。
②、复杂依赖
如果这个类或者接口不依赖其他的类或者接口差异不明显,如果依赖的类比较多的情况下(A依赖B,B又依赖C,C又依赖D,D又依赖其他)自己实例化会很麻烦。要创建A, 要先B、C、D先new一遍再new A。用ioc就快多了,A(B b),其他自动创建,是不是快多了。
总结:在程序中如果不是必须同一个对象多个实例时,也就是一个对象只是在某个地方使用一下时new一下,依赖注入就比new一个对象更好,因为new一个对象必选面临频繁创建和销毁内存实例对象的问题。而ioc管控下实例对象都是单例模式的,就是在程序运行时始终只有一个对象实例生成不需要频繁创建和销毁,也因为在内存中只有一个实例对象,减少内存开销。
(4)描述一下DispatcherServlet的工作流程?
(5)SpringMVC如何区分控制器返回的是页面还是数据(比如JSON格式的数据)
使用@ResponseBody注解,该注解用于将Controller方法返回的对象,通过适当的HttpMessageConverter转化为指定格式后,写入到Response对象的body数据区。
使用时机:返回的数据不是html标签的页面,而是其他某种格式的数据时(如json,xml等)。(如果是在程序中返回的html页面代码,也可以使用@ResponseBody,在HttpServletResponse写入,设置ContentType为text/html)
(6)Spring全家桶有哪些?
Spring、Spring MVC、Spring Boot、Spring Cloud 、 Spring Security 、Spring Data
(7)Spring普通类与工具类如何调用service层方法,为什么不能直接使用注解调用?
Spring中的Service不是你想new就能new的,因为通过new实例化的对象脱离了Spring容器的管理,获取不到注解的属性值,所以会是null,就算调用service的类中有@Component注解加入了Spring容器管理,也还是null。
新建SpringContextUtil类,在application.xml配置SpringContextUtil,最后使用DictService dictService = (DictService) SpringContextUtil.getBean("dictService");
2、Springboot:
(1) SpringBoot中如何进行单元测试?
导入spring-boot-starter-test依赖。测试类使用注解@SpringBootTest,测试的方法上加@Test注解。
(2) SpringBootApplication注解的作用。
@SpringBootApplication注解是一个组合注解,@SpringBootApplication注解的源码我们发现,它是由ComponentScan、SpringBootConfiguration、EnableAutoConfiguration等注解组合而成:
(3) 在一个Springboot+mybatis+mysql+oracle+redis+aop功能的项目中,在pom.xml中需要引入哪些jar包依赖?
进一步提问:以redis为例,springboot 1.x与springboot 2.x引入的jar包有何不同?
在 springboot 1.5.x版本的默认的Redis客户端是Jedis实现的,springboot 2.x版本中默认客户端是用lettuce实现的。
3、SpringCloud:
(1)springCloud的核心组件有哪些,解决什么问题?
Eureka(注册中心)
每个微服务都有一个EurekaClient组件,专门负责将这个服务的信息注册到EurekaServer中,也就是告诉EurekaServer,自己在哪台机器上,监听着哪个端口。而EurekaServer是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号。
Feign(REST客户端)
Feign是一个声明式REST客户端,主要是为了简便服务调用,更快捷、优雅地调用HTTPAPI。主要是实现原理是用动态代理,你要是调用哪个接口,本质就是调用Feign创建的动态代理。
Ribbon(负载均衡)
Ribbon的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀地把请求分发到各个机器上,默认使用的最经典的RoundRobin轮询算法(如果发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推)
Hystrix(熔断器)
微服务框架是许多服务互相调用的,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。Hystrix是隔离、熔断以及降级的一个框架。如果调用某服务报错或者挂了,就对该服务熔断,在5分钟内请求此服务直接就返回一个默认值,不需要每次都卡几秒,这个过程,就是所谓的熔断。但是熔断了之后就会少调用一个服务,此时需要做下标记,标记本来需要做什么业务,但是因为服务挂了,暂时没有做,等熔断的服务恢复了,就可以手工处理这些业务。这个过程,就是所谓的降级。
Zuul(服务网关)
Zuul微服务网关,负责网络路由。假设你后台部署了几百个服务,现在有个前端兄弟要来调用这些服务,难不成你让他把所有服务的名称和地址全部记住,这是不现实的,所以一般微服务架构中都必然会设计一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。
总结步骤:①服务注册—》②服务发现—》③负载均衡—》④服务调用—》⑤隔离、熔断与降级—》⑥网关路由
流程说明:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里。服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台。基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求。发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题。如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务。
(2)SpringCloud和Dubbo两种微服务架构有何区别?
Dubbo的定位始终是一款RPC框架,而SpringCloud的目标是微服务架构下的一站式解决方案。如果非要比较的话,Dubbo可以类比到NetflixOSS技术栈,而SpringCloud集成了NetflixOSS作为分布式服务治理解决方案,但除此之外SpringCloud还提供了配置、消息、安全、调用链跟踪等分布式问题解决方案。
(3)SpringBoot和SpringCloud 侧重点分别在哪些方面?
SpringBoot是Spring的一套快速配置脚手架,可以基于SpringBoot快速开发单个微服务,SpringCloud是一个基于SpringBoot实现的云应用开发工具;
SpringBoot专注于快速、方便集成的单个微服务个体,SpringCloud关注全局的服务治理框架;
SpringBoot使用了默认大于配置的理念,很多集成方案已经帮你选择好了,能不配置就不配置,SpringCloud很大的一部分是基于SpringBoot来实现。SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。
(4)springcloud如何实现服务的注册和调用?
1)服务发布时,指定对应的服务名(服务名包括了IP地址和端口),将服务注册到注册中心(eureka 或者zookeeper)
2)注册中心加@EnableEurekaServer,服务用@EnableDiscoveryClient,然后用ribbon或feign进行服务直接的调用发现。(这一过程是springcloud自动实现 只需要在main方法添加@EnableDisscoveryClient。同一个服务修改端口就可以启动多个实例)
4、mybatis:
(1)Mybatis中mapper.xml映射文件,通常都会写一个Mapper接口与之对应,这个Mapper层接口是怎么能够找到指定xml下的方法的?
Mapper接口是没有实现类的,当调用接口方法时,接口全限名(就是映射文件中的namespace的值)+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为一个MapperStatement对象。
(2)Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;原因就是namespace+id是作为Map<String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
(3)Mybatis中使用MySQL和Oracle分页的区别
MySQL分页:(利用LIMIT关键字)计算参数为开始序号(startNum),要查的总条数(totalNum)
Oracle分页:(利用自带的rownum)计算参数为开始序号(startNum),结束序号(endNum)【注意:Oracle分页利用其自带的rownum,但是rownum在表中不能使用>号(比如select rownum,a.* from A a where rownum > n,查出的都是空),但是可以使用<。这是因为rownum是一个总是从1开始的伪列,Oracle认为rownum>n(n>1的自然数)这种条件依旧不成立,所以查不到记录】
(4)Mybatis是如何将sql执行结果封装为目标对象并返回的?
第一种是使用resultMap标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。
进一步提问:resultMap和resultType有啥区别?
resultType:当使用resultType做SQL语句返回结果类型处理时,对于SQL语句查询出的字段在相应的pojo中必须有和它相同的字段对应,而resultType中的内容就是pojo在本项目中的位置。
resultMap:当使用resultMap做SQL语句返回结果类型处理时,通常需要先在mapper.xml中定义resultMap进行pojo和相应表字段的对应关系。然后再使用resultMap。
(5)Mybatis xml映射文件中,除了常见的select、insert、updae、delete标签之外,还有哪些标签及其作用?
除了这四个标签,还有<resultMap>、<parameterMap>、<sql>、<include>、<selectKey>,一个9个标签,其中<sql>为sql片段标签,通过<include>标签引入sql片段,<selectKey>为不支持自增的主键生成策略标签。
(6)为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
hibernate对很多数据库的操作已经进行了封装,hibernate操作对象时,比如往数据库添加一条记录,直接save就可以了。(Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取),而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,虽然现在已经有不少程序可以自动生成xml文件,但还是需要自己调整sql,所以称之为半自动ORM映射工具。这也从侧面可以看出hibernate的拓展性不如Mybatis(hibernate做了很多封装)。
(7)mybatis 为什么大于不用转义,小于必须转义?
mybatis不支持“<”,本质是xml不支持这个符号,<会引起xml格式的错误,xml文件中的标签是 <…> 这种形式的, 所以当出现 “<” 号时, 会认为是一个标签的开始。
gt; 导出word我们常用的是通过POI实现导出。POI最擅长的是EXCEL的操作。word操作起来样式控制还是太繁琐了。今天我们介绍下通过FREEMARK来实现word模板导出。
[TOC]
# 开发准备
- 本文实现基于springboot,所以项目中采用的都是springboot衍生的产品。首先我们在maven项目中引入freemark坐标。
```xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId></dependency>
```
- 只需要引入上面的jar包 。 前提是继承springboot坐标。就可以通过freemark进行word的导出了。
## 模板准备

- 上面是我们导出的一份模板。填写规则也很简单。只需要我们提前准备一份样本文档,然后将需要动态修改的通过`${}`进行占位就行了。我们导出的时候提供相应的数据就行了。这里注意一下`${c.no}`这种格式的其实是我们后期为了做集合遍历的。这里先忽略掉。后面我们会着重介绍。
## 开发测试
- 到了这一步说明我们的前期准备就已经完成了。剩下我们就通过freemark就行方法调用导出就可以了。
- 首先我们构建freemark加载路径。就是设置一下freemark模板路径。模板路径中存放的就是我们上面编写好的模板。只不过这里的模板不是严格意义的word.而是通过word另存为xml格式的文件。

- 配置加载路径
```//创建配置实例Configuration configuration = new Configuration();//设置编码configuration.setDefaultEncoding("UTF-8");//ftl模板文件configuration.setClassForTemplateLoading(OfficeUtils.class, "/template");
```
- 获取模板类
```
Template template = configuration.getTemplate(templateName);
```
- 构建输出对象
```
Writer out = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
```
- 导出数据到out
```
template.process(dataMap, out);
```
- 就上面四步骤我们就可以实现导出了。我们可以将加载配置路径的放到全局做一次。剩下也就是我们三行代码就可以搞定导出了。当然我们该做的异常捕获这些还是需要的。[点我获取源码](https://gitee.com/zxhTom/office-multip.git)
# 结果检测

# 功能通用化思考
- 上面我们只是简单介绍一下freemark导出word的流程。关于细节方面我们都没有进行深究。 - 细心的朋友会发现上面的图片并没有进行动态的设置。这样子功能上肯定是说不过去的。图片我们想生成我们自己设置的图片。 - 还有一个细节就是复选框的问题。仔细观察会发现复选框也没有字段去控制。肯定也是没有办法进行动态勾选的。 - 最后就是我们上面提到的就是主要安全措施那块。那块是我们的集合数据。通过模板我们是没法控制的。 - 上面的问题我们freemark的word模板是无法实现的。有问题其实是好事。这样我们才能进步。实际上freemark导出真正是基于ftl格式的文件的。只不过xml和ftl语法很像所以上面我们才说导出模板是xml的。实际上我们需要的ftl文件。如果是ftl文件那么上面的问题的复选框和集合都很好解决了。一个通过if标签一个通过list标签就可以解决了。图片我们还是需要通过人为去替换
```
<#if checkbox ??&& checkbox?seq_contains('窒息;')?string('true','false')=='true'>0052<#else>00A3</#if>
<#list c as c>dosomethings()</#list>
```
- 上面两段代码就是if 和 list语法
# Dom4j实现智能化
- 上面ftl虽然解决了导出的功能问题。但是还是不能实现智能化。我们想做的其实想通过程序自动根据我们word的配置去进行生成ftl文件。经过百度终究还是找到了对应的方法。Dom4j就是我们最终方法。我们可以通过在word进行特殊编写。然后程序通过dom4j进行节点修改。通过dom4j我们的图片问题也就迎刃而解了。下面主要说说针对以上三个问题的具体处理细节
## 复选框

- 首先我们约定同一类型的复选框前需要`#{}`格式编写。里面就是控制复选框的字段名。 - 然后我们通过dom4j解析xml。我们再看看复选框原本的格式在xml中
```<w:sym w:font="Wingdings 2" w:char="0052"/>
``` - 那么我们只需要通过dom4j获取到w:sym标签。在获取到该标签后对应的文本内容即#{zhuyaoweihaiyinsu}窒息;这个内容。 - 匹配出字段名zhuyaoweihaiyinsu进行if标签控制内容
```
<#if checkbox ??&& checkbox?seq_contains('窒息')?string('true',false')=='true'>0052<#else>00A3</#if>
```
### 部分源码
```java
Element root = document.getRootElement();List<Element> checkList = root.selectNodes("//w:sym");List<String> nameList = new ArrayList<>();Integer indext = 1;for (Element element : checkList) { Attribute aChar = element.attribute("char"); String checkBoxName = selectCheckBoxNameBySymElement(element.getParent()); aChar.setData(chooicedCheckBox(checkBoxName));}
```
## 集合

- 同样的操作我们通过获取到需要改变的标签就可以了。集合和复选框不一样。集合其实是我们认为规定出来的一种格式。在word中并没有特殊标签标示。所以我们约定的格式是`${a_b}`。首先我们通过遍历word中所以文本通过正则验证是否符合集合规范。符合我们获取到当前的行然后在行标签前添加#list标签。 然后将${a_b}修改成${a.b} 至于为什么一开始不设置a.b格式的。我这里只想说是公司文化导致的。我建议搭建如果是自己实现这一套功能的话采用a.b格式最好。 ### 部分源码
```java
Element root = document.getRootElement(); //需要获取所有标签内容,判断是否符合 List<Element> trList = root.selectNodes("//w:t"); //rowlist用来处理整行数据,因为符合标准的会有多列, 多列在同一行只需要处理一次。 List<Element> rowList = new ArrayList<>(); if (CollectionUtils.isEmpty(trList)) { return; } for (Element element : trList) { boolean matches = Pattern.matches(REGEX, element.getTextTrim()); if (!matches) { continue; } //符合约定的集合格式的才会走到这里 //提取出tableId 和columnId Pattern compile = Pattern.compile(REGEX); Matcher matcher = compile.matcher(element.getTextTrim()); String tableName = ""; String colName = ""; while (matcher.find()) { tableName = matcher.group(1); colName = matcher.group(2); } //此时获取的是w:t中的内容,真正需要循环的是w:t所在的w:tr,这个时候我们需要获取到当前的w:tr List<Element> ancestorTrList = element.selectNodes("ancestor::w:tr[1]"); /*List<Element> tableList = element.selectNodes("ancestor::w:tbl[1]"); System.out.println(tableList);*/ Element ancestorTr = null; if (!ancestorTrList.isEmpty()) { ancestorTr = ancestorTrList.get(0); //获取表头信息 Element titleAncestorTr = DomUtils.getInstance().selectPreElement(ancestorTr); if (!rowList.contains(ancestorTr)) { rowList.add(ancestorTr); List<Element> foreachList = ancestorTr.getParent().elements(); if (!foreachList.isEmpty()) { Integer ino = 0; Element foreach = null; for (Element elemento : foreachList) { if (ancestorTr.equals(elemento)) { //此时ancestorTr就是需要遍历的行 , 因为我们需要将此标签扩容到循环标签汇中 foreach = DocumentHelper.createElement("#list"); foreach.addAttribute("name", tableName+" as "+tableName); Element copy = ancestorTr.createCopy(); replaceLineWithPointForeach(copy); mergeCellBaseOnTableNameMap(titleAncestorTr,copy,tableName); foreach.add(copy); break; } ino++; } if (foreach != null) { foreachList.set(ino, foreach); } } } else { continue; } } }
```
## 图片

- 图片和复选框类似。因为在word的xml中是通过特殊标签处理的。但是我们的占位符不能通过以上占位符占位了。需要一张真实的图片进行占位。因为只有是一张图片word才会有图片标签。我们可以在图片后通过`@{imgField}`进行占位。然后通过dom4j将图片的base64字节码用${imgField}占位。
### 部分源码
```java
//图片索引下表Integer index = 1;//获取根路径Element root = document.getRootElement();//获取图片标签List<Element> imgTagList = root.selectNodes("//w:binData");for (Element element : imgTagList) { element.setText(String.format("${img%s}",index++)); //获取当前图片所在的wp标签 List<Element> wpList = element.selectNodes("ancestor::w:p"); if (CollectionUtils.isEmpty(wpList)) { throw new DomException("未知异常"); } Element imgWpElement = wpList.get(0); while (imgWpElement != null) { try { imgWpElement = DomUtils.getInstance().selectNextElement(imgWpElement); } catch (DomException de) { break; } //获取对应图片字段 List<Element> imgFiledList = imgWpElement.selectNodes("w:r/w:t"); if (CollectionUtils.isEmpty(imgFiledList)) { continue; } String imgFiled = getImgFiledTrimStr(imgFiledList); Pattern compile = Pattern.compile(REGEX); Matcher matcher = compile.matcher(imgFiled); String imgFiledStr = ""; while (matcher.find()) { imgFiledStr = matcher.group(1); boolean remove = imgWpElement.getParent().elements().remove(imgWpElement); System.out.println(remove); } if (StringUtils.isNotEmpty(imgFiledStr)) { element.setText(String.format("${%s}",imgFiledStr)); break; } }
}
```
# 基于word自动化导出(含源码)
- 以上就是我们实现导出的流程。通过上面的逻辑我们最终可以一套代码复用了。源码下载地址:https://gitee.com/zxhTom/office-multip.git
###### 参考网络文章
[dom操作xml](https://www.cnblogs.com/alsf/p/9278816.html)[dom生成xml](https://www.cnblogs.com/it-mh/p/11021716.html)[httpclient获取反应流](https://blog.csdn.net/qw222pzx/article/details/97884917)[获取jar路径](https://blog.csdn.net/liangcha007/article/details/88526181)[itext实现套打](https://blog.csdn.net/flyfeifei66/article/details/6739950)[ftl常见语法](https://www.cnblogs.com/zhaoYuQing-java2015/p/6046697.html)[freemark官网](https://freemarker.apache.org/docs/ref_directive_list.html)[ftl判断非空](https://www.iteye.com/blog/lj6684-1594769)[freemark自定义函数](https://blog.csdn.net/weixin_34174422/article/details/91867563)[freemark自定义函数java](https://blog.csdn.net/hzgzf/article/details/83399351)[freemark特殊字符转义](https://blog.csdn.net/arsenic/article/details/8490098)[java实现word转xml各种格式](https://www.cnblogs.com/Yesi/p/11195732.html)
[加入战队](#addMe)
# # <span id="addMe">加入战队</span>
## 微信公众号

Thymeleaf 是新一代 Java 模板引擎,与 Velocity、FreeMarker 等传统 Java 模板引擎不同,Thymeleaf 支持 HTML 原型,其文件后缀为“.html”,因此它可以直接被浏览器打开,此时浏览器会忽略未定义的 Thymeleaf 标签属性,展示 thymeleaf 模板的静态页面效果;当通过 Web 应用程序访问时,Thymeleaf 会动态地替换掉静态内容,使页面动态显示。
html<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--th:text 为 Thymeleaf 属性,用于在展示文本-->
<h1 th:text="欢迎您来到Thymeleaf">欢迎您访问静态页面 HTML</h1>
</body>
</html>
当直接使用浏览器打开时,浏览器展示结果如下。
欢迎您访问静态页面HTML
当通过 Web 应用程序访问时,浏览器展示结果如下。
欢迎您来到Thymeleaf
Thymeleaf 的特点:
在使用 Thymeleaf 之前,首先要在页面的 html 标签中声明名称空间,示例代码如下。
htmlxmlns:th="http://www.thymeleaf.org"
在 html 标签中声明此名称空间,可避免编辑器出现 html 验证错误,但这一步并非必须进行的,即使我们不声明该命名空间,也不影响 Thymeleaf 的使用。
使用${}包裹的表达式被称为变量表达式,该表达式具有以下功能:
选择变量表达式与变量表达式功能基本一致,只是在变量表达式的基础上增加了与 th:object 的配合使用。当使用 th:object 存储一个对象后,我们可以在其后代中使用选择变量表达式(*{...})获取该对象中的属性,其中,“*”即代表该对象。
html<div th:object="${session.user}" >
<p th:text="*{fisrtName}">firstname</p>
</div>
th:object 用于存储一个临时变量,该变量只在该标签及其后代中有效,在后面的内容“th 属性”中我详细介绍。
不管是静态资源的引用,还是 form 表单的请求,凡是链接都可以用链接表达式 (@{...})。
链接表达式的形式结构如下:
例如使用链接表达式引入 css 样式表,代码如下。
html<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
消息表达式一般用于国际化的场景。结构如下。
htmlth:text="#{msg}"
注意:此处了解即可,我们会在后面的章节中详细介绍。
片段引用表达式用于在模板页面中引用其他的模板片段,该表达式支持以下 2 中语法结构:
以上语法结构说明如下:
Thymeleaf 还提供了大量的 th 属性,这些属性可以直接在 HTML 标签中使用,其中常用 th 属性及其示例如下表。
属性 | 描述 | 示例 |
th:id | 替换 HTML 的 id 属性 | <input id="html-id" th:id="thymeleaf-id" /> |
th:text | 文本替换,转义特殊字符 | <h1 th:text="hello,bianchengbang" >hello</h1> |
th:untext | 文本替换,文本替换,不转义特殊字符 | <div th:utext="'<h1>欢迎来到编程帮!</h1>'" >欢迎你</div> |
th:object | 在父标签选择对象,子标签使用 *{…} 选择表达式选取值。没有选择对象,那子标签使用选择表达式和 ${…} 变量表达式是一样的效果。同时即使选择了对象,子标签仍然可以使用变量表达式。 | <div th:object="${session.user}" > <p th:text="*{fisrtName}">firstname</p> </div> |
th:value | 替换 value 属性 | <input th:value = "${user.name}" /> |
th:with | 局部变量赋值运算 | <div th:with="isEvens = ${prodStat.count}%2 == 0" th:text="${isEvens}"></div> |
th:style | 设置样式 | <div th:style="'color:#F00; font-weight:bold'">编程帮 www.biancheng.net</div> |
th:onclick | 点击事件 | <td th:onclick = "'getInfo()'"></td> |
th:each | 遍历,支持 Iterable、Map、数组等。 | <table> <tr th:each="m:${session.map}"> <td th:text="${m.getKey()}"></td> <td th:text="${m.getValue()}"></td> </tr></table> |
th:if | 根据条件判断是否需要展示此标签 | <a th:if ="${userId == collect.userId}"> |
th:unless | 和 th:if 判断相反,满足条件时不显示 | <div th:unless="${m.getKey()=='name'}" ></div> |
th:switch | 与 Java 的 switch case语句类似通常与 th:case 配合使用,根据不同的条件展示不同的内容 | <div th:switch="${name}"> <span th:case="a">编程帮</span> <span th:case="b">www.biancheng.net</span></div> |
th:fragment | 模板布局,类似 JSP 的 tag,用来定义一段被引用或包含的模板片段 | <footer th:fragment="footer">插入的内容</footer> |
th:insert | 布局标签;将使用 th:fragment 属性指定的模板片段(包含标签)插入到当前标签中 | <div th:insert="commons/bar::footer"></div> |
th:replace | 布局标签;使用 th:fragment 属性指定的模板片段(包含标签)替换当前整个标签 | <div th:replace="commons/bar::footer"></div> |
th:selected | select 选择框选中 | <select> <option>---</option> <option th:selected="${name=='a'}"> 编程帮 </option> <option th:selected="${name=='b'}"> www.biancheng.net </option></select> |
th:src | 替换 HTML 中的 src 属性 | <img th:src="@{/asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" /> |
th:inline | 内联属性;该属性有 text、none、javascript 三种取值,在 <script> 标签中使用时,js 代码中可以获取到后台传递页面的对象。 | <script type="text/javascript" th:inline="javascript"> var name = /*[[${name}]]*/ ianchengbang'; alert(name)</script> |
th:action | 替换表单提交地址 | <form th:action="@{/user/login}" th:method="post"></form> |
在 Web 项目中,通常会存在一些公共页面片段(重复代码),例如头部导航栏、侧边菜单栏和公共的 js css 等。我们一般会把这些公共页面片段抽取出来,存放在一个独立的页面中,然后再由其他页面根据需要进行引用,这样可以消除代码重复,使页面更加简洁。
Thymeleaf 作为一种优雅且高度可维护的模板引擎,同样支持公共页面的抽取和引用。我们可以将公共页面片段抽取出来,存放到一个独立的页面中,并使用 Thymeleaf 提供的 th:fragment 属性为这些抽取出来的公共页面片段命名。 示例 1 将公共页面片段抽取出来,存放在 commons.html 中,代码如下。
html<div th:fragment="fragment-name" id="fragment-id">
<span>公共页面片段</span>
</div>
在 Thymeleaf 中,我们可以使用以下 3 个属性,将公共页面片段引入到当前页面中。
使用上 3 个属性引入页面片段,都可以通过以下 2 种方式实现。
通常情况下,
{} 可以省略,其行内写法为 [[{...}]] 或 [({...})],其中 [[{...}]] 会转义特殊字符,[(~{...})] 则不会转义特殊字符。
示例 2
html<!--th:insert 片段名引入-->
<div th:insert="commons::fragment-name"></div>
<!--th:insert id 选择器引入-->
<div th:insert="commons::#fragment-id"></div>
------------------------------------------------
<!--th:replace 片段名引入-->
<div th:replace="commons::fragment-name"></div>
<!--th:replace id 选择器引入-->
<div th:replace="commons::#fragment-id"></div>
------------------------------------------------
<!--th:include 片段名引入-->
<div th:include="commons::fragment-name"></div>
<!--th:include id 选择器引入-->
<div th:include="commons::#fragment-id"></div>
html<!--th:insert 片段名引入-->
<div>
<div id="fragment-id">
<span>公共页面片段</span>
</div>
</div>
<!--th:insert id 选择器引入-->
<div>
<div id="fragment-id">
<span>公共页面片段</span>
</div>
</div>
------------------------------------------------
<!--th:replace 片段名引入-->
<div id="fragment-id">
<span>公共页面片段</span>
</div>
<!--th:replace id 选择器引入-->
<div id="fragment-id">
<span>公共页面片段</span>
</div>
------------------------------------------------
<!--th:include 片段名引入-->
<div>
<span>公共页面片段</span>
</div>
<!--th:include id 选择器引入-->
<div>
<span>公共页面片段</span>
</div>
Thymeleaf 在抽取和引入公共页面片段时,还可以进行参数传递,大致步骤如下: 1.传入参数; 2.使用参数。
引用公共页面片段时,我们可以通过以下 2 种方式,将参数传入到被引用的页面片段中: 模板名::选择器名或片段名(参数1=参数值1,参数2=参数值2) 模板名::选择器名或片段名(参数值1,参数值2)
注:若传入参数较少时,一般采用第二种方式,直接将参数值传入页面片段中; 若参数较多时,建议使用第一种方式,明确指定参数名和参数值,。
示例代码如下:
java<!--th:insert 片段名引入-->
<div th:insert="commons::fragment-name(var1='insert-name',var2='insert-name2')"></div>
<!--th:insert id 选择器引入-->
<div th:insert="commons::#fragment-id(var1='insert-id',var2='insert-id2')"></div>
------------------------------------------------
<!--th:replace 片段名引入-->
<div th:replace="commons::fragment-name(var1='replace-name',var2='replace-name2')"></div>
<!--th:replace id 选择器引入-->
<div th:replace="commons::#fragment-id(var1='replace-id',var2='replace-id2')"></div>
------------------------------------------------
<!--th:include 片段名引入-->
<div th:include="commons::fragment-name(var1='include-name',var2='include-name2')"></div>
<!--th:include id 选择器引入-->
<div th:include="commons::#fragment-id(var1='include-id',var2='include-id2')"></div>
在声明页面片段时,我们可以在片段中声明并使用这些参数,例如:
java<!--使用 var1 和 var2 声明传入的参数,并在该片段中直接使用这些参数 -->
<div th:fragment="fragment-name(var1,var2)" id="fragment-id">
<p th:text="'参数1:'+${var1} + '-------------------参数2:' + ${var2}">...</p>
</div>
启动 Spring Boot,使用浏览器访问 fragment.html,结果如下图。
*请认真填写需求信息,我们会在24小时内与您取得联系。