1、对Spring IOC和AOP的理解
IOC: 全称 Inverse of Control,中文翻译为控制反转, 将原本需要手动创建的对象的过程,交由给Spring框架来完成,只需要在用的时候注入对象就可以直接使用,简化了手动创建对象的过程,同时,将对象的依赖关系和整个生命周期,交由给Spring框架来管理,极大程度上降低了开发的复杂度和项目的维护程度。
IOC容器本身其实就是一个Map结构,保存了对象名和对象之间的引用关系。Spring中,BeanFactory就是我们常见的Spring最顶级的IOC容器接口,定义了IOC容器相关的方法。
DI: Dependency Injection 依赖注入,一个对象与另一个对象之间的依赖关系,是由Spring进行管理,Spring容器在对象初始化过程中,就会将依赖关系对应好,这个过程就是注入的过程。
AOP: AOP就是面向切面编程,将一些共性的逻辑,可以抽象成一个切面,减少对业务逻辑的侵入,如:日志管理、监控管理、权限管理等等,降低模块间的耦合和简化重复代码。Spring AOP 是基于动态代理实现,有两种动态代理的方式:JDK动态代理和CGLIB动态代理,代理类是不是实现某一个接口,决定了选择使用哪种代理方式。
2、DI注入的几种方式 Bean的注册与注入方式
常见的几种方式(有些文档上面写的是:setter
注入,构造器注入,接口注入,不过接口注入基本上被废弃了):
1、setter
方式注入
2、构造器注入
3、基于@Autowired
注解的Field
注入
@Autowired
和@Resource
都可以实现自动注入,@Autowired
是Spring提供的规范,@Resource
是J2EE提供规范,Spring对这个注解做了支持,功能更为强大。
1、注入方式有区别: @Autowired
只能提供byType
的注入方式,按照类型进行注入,没有其他的注入方式; @Resource
提供了byName
和byType
两种方式,默认使用byName
的注入方式,根据名字进行注入,可以通过name
属性,进行设置;如果无法注入,则再使用byType
的方式进行注入,可以通过type
属性进行进行指定。
@Resource(name = "add",type= Object.class)
2、@Autowired在setter、field、构造器上都可以使用,@Resource不能用于构造器上面
Spring官方推荐使用构造器注入的方式,缺点就是如果注入的参数过多,会显得很臃肿,但是过多参数这种情况,可以通过合理的规划和解耦来规避,满足单一职责的原则。
1、可以保证依赖不可变,使用final修饰要注入的变量
2、依赖不为空,避免了field和setter注入时,出现的空指针问题
3、完全初始化,避免了循环依赖,在使用时,保证了是一个初始化完成的对象。
可以参考这篇文章:浅谈spring为什么推荐使用构造器注入和《Spring揭秘》这本书
2、SpringBean的作用域
- singleton: 单例,Spring Bean的默认作用域都是单例
- prototype: 原型模式,每次都生成一个新的实例
- session:用于web里面,每个会话生成会有一个新的实例
- request: 用于web里面,每次请求会生成一个新的实例
- global-session:全局session作用域,仅仅在基于portlet的web应用中才有意义
3、动态代理和静态代理
代理其实就是一种设计模式,主要是用一个类代替另一个类的所有功能,并在其基础上增加一些自己的变化。代理模式的详细介绍和用法可以参考 代理模式
静态代理:是一种编译时增强的代理方式,在代码里面,就定义好了被代理对象和代理类,编译后,就已经生成了相关的字节码和class文件,运行时会直接使用代理类的字节码
动态代理:动态代理是一种运行时增强的代理方式,运行时将字节码加载到JVM里面,实现运行时增强。动态代理的实现方式有两种:JDK动态代理和CGLIB动态代理两种。
静态代理相较于动态代理而言,结构更加固定,效率要更高一些,但是不够灵活,而且一个代理类,一般只能代理一类的操作,无法代理多个,如果有多个对象需要被代理,则需要创建多个代理类。
JDK 动态代理
JDK 动态代理的核心就是InvocationHandler接口,通过反射的机制实现对接口代理,newProxyInstance里面,会对被代理类是不是实现接口进行判断。
/**
* 要定义接口,JDK动态代理,必须定义接口,原因的话可以看Proxy.class 第588行,里面有一个判断是不是接口* 的if判断
**/
public interface Subject {
void seeHello();
}
// 必须要实现一个InvocationHandler类,才可以使用JDK动态代理
public class SubjectInnvocationHandler implements InvocationHandler {
private Object target;
public SubjectInnvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(target, args);
return null;
}
}
public class SubjectImpl implements Subject {
@Override
public void seeHello() {
System.out.println("韩梅梅跟李雷打招呼");
}
}
public static void main(String[] args) {
Subject proxy = (Subject) Proxy.newProxyInstance(Subject.class.getClassLoader(),
new Class[]{Subject.class}, new SubjectInnvocationHandler(new SubjectImpl()));
// 调用方法
proxy.seeHello();
}
JDK 动态代理的本质,实际上就是对代理的对象,增加一个新的接口实现,然后通过反射的方式,进行调用。
CGLIB 动态代理
CGLIB动态代理,底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类,CGLIB代理首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
public class HelloService {
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
public static void main(String[] args) {
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
JDK 动态代理和CGLIB 动态代理有什么区别
1、JDK动态代理是基于反射机制,而CGLIB并不是基于反射,而是通过生成一个继承自FastClass的类,通过方法的内部直接调用,来实现代理。
2、JDK动态代理必须要求代理类实现某一个接口,而CGLIB则没有这个要求,因为CGLIB是实现的被代理类的子类。
3、JDK动态代理可以代理private和final的类,但是CGLIB不能代理final或者private或static的类或者方法。
FastClass
上面反复提到了CGLIB动态代理,使用的是FastClass实现类,FastClass是通过将代理类的每个方法,根据方法名+参数等,生成一个索引值,通过索引值就可以定位要请求的方法,并返回执行的结果。上一段代码就可以看明白了
public class DelegateClass {
private String type;
public DelegateClass() {
}
public DelegateClass(String string) {
this.type = string;
}
public boolean add(String string, int i) {
System.out.println("This is add method: " + string + ", " + i);
return true;
}
public void update() {
System.out.println("This is update method");
}
}
// FastClass动态子类实例
FastClass fastClass = FastClass.create(DelegateClass.class);
// 创建委托类实例
DelegateClass fastInstance = (DelegateClass) fastClass.newInstance(
new Class[] {String.class}, new Object[]{"Jack"});
// 调用委托类方法,可以返回结果
boolean result = (boolean)fastClass.invoke("add", new Class[]{ String.class, int.class},
fastInstance, new Object[]{ "Jack", 25});
fastClass.invoke("update", new Class[]{}, fastInstance, new Object[]{});
FastClass相比较反射的方式,简单了许多,尤其对于某个没有实现接口的类,也是可以进行代理。 可以参考这篇文章 cglib源码分析
4、Spring AOP中的常见定义和5种通知方式
Aspect
: 切面,可以理解为定义的每一个AOP对象,一般用注解@Aspect
修饰
JoinPoint
: 连接点,程序执行过程中的每一个操作,可以理解为类的每一个方法都可以作为一个连接点
PointCut
:切入点,切入点跟连接点是相互关联的,连接点是表示类中的一个个方法,切入点就是定义了规则,定义哪些连接点是需要进入到我们的切面程序的。PointCut的表达式定义,是可以拦截某一个确定的方法,或者一类方法,或者某一个注解。
Advice
:通知,匹配切入点之后,可以通过Advice来定义切面执行的前后顺序,即指定了这些 joinpoint 被织入(或者增强)的具体时机与逻辑,是切面代码真正被执行的地方。目前切面有5种通知方式:
- Before:前置通知 对应@Before
- After:后置通知 对应@After
- Return:返回通知 对应@AfterReturning
- Around:环绕通知 对应@
- Exception:异常通知 对应@AfterThrowing
这几种通知的执行顺序:
单个切面,正常执行情况
单个切面,异常情况:
这边解释一下第4步,我自己用around的时候,通常是这么用:
log.info("[around] 环绕前置通知");
Stopwatch stopwatch = Stopwatch.createStarted();
Object result = null;
try{
joinPoint.proceed();
cost = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
}catch (Exception e){
log.error("[around] 环绕异常通知",e);
}finally{
log.info("[around] 环绕后置通知");
}
如果发生的异常,被catch捕获,则不会抛出AfterThrowing异常,如果没有捕获,则会进入到这个异常里面。
多个切面,跟单个切面类似,递归的执行,且可以通过Order指定AOP执行的先后顺序。
5、BeanFactory和FactoryBean的区别
BeanFactory
和FactoryBean
本身,除了名字相似,其实并没有太多的联系,BeanFactory
是Spring
的顶级容器接口,是一个Bean
的工厂对象,用于生成和管理我们自己定义的Bean
,使用的简单工厂模式
,通过BeanDefinition
定义的Bean
的格式,生成相应的Bean
对象。
FactoryBean
本身是一个Bean
对象,可以用来生成一些复杂的Bean对象,FactoryBean
提供了一个getObject
的方法,这个方法是可以被重写,可以生成任意的Bean
对象。FactoryBean
本身使用了工厂方法这个设计模式。其实这个地方,可以这么想,本身我们自己定义的Bean
都是要按照BeanDefinition
的模式来定义,要是我们想自己定义一些区别与SpringBean
的格式,比如说重新定义作用域等等,就可以用这个FactoryBean
来生成一些复杂的对象。
6、FactoryBean
和ObjectFactory
ObjectFactory跟FactoryBean很类似,都有一个getObject方法,都可以创建对象,但是FactoryBean创建的Bean对象,是由Spring容器来管理作用域,而ObjectFactory则必须自己管理作用域。
7、BeanFactory和ApplicationContext
BeanFactory是顶级的容器接口,ApplicationContext是BeanFactory的子接口,相比较BeanFactory,ApplicationContext更为复杂,主要有以下几个地方:
- 增加多个Bean返回的接口:
BeanFactory
本身只提供了单个Bean
对象的获取,ApplicationContext
可以根据Bean
的名字获取多个Bean
的List
,其次,也继承了HierarchicalBeanFactory
,实现层次结构或父子结构等。 - 增加国际化(
MessageSource
):增加了国际化的支持 - 多路径配置(
ResourceLoader
):支持ClassPath Xml
和FileSystem XML
等多种配置方式。 - 事件发布机制(
ApplicationEventPublisher
):增加了事件监听和事件发布。
8、Spring中常见的9大设计模式
- 简单工厂模式:
BeanFactory
创建对象,用的就是这个简单工厂模式,又叫做静态工厂模式 - 工厂方法:
FactoryBean
用的是这种方式 - 代理模式:这个最常见,AOP、拦截器等都是用的
JDK动态代理或者CGLIB动态代理
。 - 观察者模式:事件发布和监听用到的是观察者模式
- 策略模式:像配置文件加载的方式,里面用的就是策略模式,可以选择FileSystem加载或者Classpath下面加载xml文件。
- 装饰器模式:一般带有
Wrapper
的,比如BeanWrapper
都是用的是装饰器模式,用来增强对象的属性。 - 适配器模式:在
Spring MVC
里面,比较重要的HandlerAdapter
就是适配器模式,用来适配不同类型的Handler
- 模板方法:
RedisTemplate、jdbcTemplate
都是这个 - 单例模式:最最重要的单例模式,在Spring里面最常见,Bean的作用域就是单例的
上面几种都需要重点掌握,其实还有一些其他的模式,比如说在
Spring MVC
里面,将Handler组装成一个HandlerExecutionChain - 责任链模式
9、Spring Bean的生命周期
- 实例化: Bean init,实现反射创建对象
- 初始化: populateBean 实现对象属性的填充,用到了3级缓存
- Aware接口实现: BeanAware接口检查,如BeanNameAware、BeanFactoryNameAware等,可以进行属性设置
- BeanPostProcesser前置处理: 在前置处理器执行时,会触发@PostConstruct的执行
- 执行InitializingBean的afterPropertiesSet方法
- 执行init-method方法
- 执行BeanPostProcessor后置方法
- 看是否实现相关的DisposableBean接口
- 执行destory方法
10、Spring Bean的常用扩展点
- 各种Aware接口的使用,以BeanNameAware为例,可以给BeanName重写
- BeanPostProcessor和BeanFactoryPostProcessor 生成Bean 和 BeanFactory的前置和后置处理,BeanFactoryPostProcessor 里面可以做变量替换,比如DataSource的占位符替换等等
- BeanPostProcessor里面可以做AOP代理和@PostConstruct初始化
- afterPropertiesSet和init-method可以做初始化等等
11、Spring里面有没有线程?
Spring框架本身只是一个容器,是一个管理Bean的容器,因此Spring框架本身是没有线程的,线程的产生是由外部的Selvet容器产生,如Tomcat容器等。
12、Spring Bean是否是线程安全
不是,Spring Bean一般是无状态的,如果有状态的,需要避免多线程并发安全问题。解决办法有很多种,比如常用的锁、无锁的cas或者使用ThreadLocal来进行线程隔离。
13、Spring拦截器和过滤器
过滤器
先看一下Filter的源码
package javax.servlet;
import java.io.IOException;
public interface Filter {
//初始化方法,容器创建Filter对象后,立即调用init方法,整个生命周期中只执行一次。
//在init方法成功(失败如抛异常等)执行完前,不能提供过滤服务。
//参数FilterConfig用于获取初始化参数
public void init(FilterConfig filterConfig) throws ServletException;
//执行过滤任务的方法,参数FilterChain表示Filter链,doFilter方法中只有执行FilterChain只有执行了doFilter方法,
//才能将请求交经下一个Filter或Servlet执行
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
//销毁方法,当移出服务时由web容器调用。整个生命周期中destroy方法只会执行一次
//destroy方法可用于释放持有的资源,如内存、文件句柄等
public void destroy();
}
Filter是基于Servlet,是由Servlet进行调用,以Tomcat为例,StandardWrapperValue中会调用doFilter方法,然后每个doFilter方法会调用下一个filter。
Filter使用的设计模式是责任链模式,是通过函数调用进行的。
Filter1->Filter2->Servlet->Filter2->Filter1 调用方式,跟递归有点类似。
过滤器只能使用在Web场景下,单一的方法调用是无法使用的,原因是因为依赖于Servlet的调用,可以做一些编码的转换,url的过滤等等
拦截器
拦截器是Spring框架提供的能力,可以做过滤器能做的一切事情,但是比过滤器更加的灵活,而且拦截器是代码层面可以实现,可以在一次请求里面多次调用,也可以实现在函数级别的拦截。
Filter->Servlet->拦截器1前置->拦截器2前置->Controller->拦截器2后置->拦截器1后置->Servlet->Filter 拦截器的实现,是基于
CGLIB动态代理
实现,与AOP的实现方式类似,但不是用的一个方法。
14、Spring Boot自动装配的原理
SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器,并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。
SpringBootApplication注解里面包含了一个重要的注解EnableAutoConfiguration,这个注解里面会有一个关键的注解,@Import注解可以注入AutoConfigurationImportSelector,这个会有selectImports方法,这个方法会读取所有的META-INF/spring.factories文件,里面会有一个EnableAutoConfigure的key-value结构的字段,来标识这是要自动装配的类对象的全限定类名,通过反射机制,将对象加载到Spring Bean容器里面;其次 META-INF/spring-autoconfigure-metadata.properties 这个里面定义了加载的条件。这是一种Java SPI机制,dubbo的核心也是SPI机制,不过相对于SpringSPI,Dubbo的SPI更灵活。
可以参考这篇文章,写的挺不错的 淘宝一面:“说一下 Spring Boot 自动装配原理呗?
15、Spring三级缓存
一级缓存:singletonObject,存放已经初始化好的单例对象
二级缓存:earlySingletonObjects早期暴露对象,半初始化对象,一般都存在这个里面
三级缓存:singletonFactories,Bean的工厂对象,用于创建Bean代理对象。
16、Spring是如何解决循环依赖连环问题
问题1:Spring是如何解决循环依赖问题
Spring是通过将对象的初始化过程分为两部分:1、实例化,通过反射创建一个空对象,2、属性填充,通过setter方法,使用反射机制,将属性设置到对象里面。通过将初始化过程分为两部分,然后再加入三级缓存,就解决了循环依赖的问题。其实,要想实现循环依赖的重要一个环节就是实现属性的填充和对象的生成,不在同时完成,懒加载也可以解决循环依赖问题。
问题2:为什么要通过三级缓存来解决循环依赖问题,1,2级缓存为什么不行
- 为什么一级缓存不能实现? 如果只有一级缓存,那么早期曝光的和已经初始化好的对象都会在一个里面,这样会有可能会存在多个线程访问的时候,没有初始化好的那种,出现空指针,所以一级缓存不行。
- 为什么只用两级缓存不行? 一级缓存,在一定基础上都可以实现,两级缓存肯定也是在一定程度上可以的,但是为什么Spring用了三级缓存,主要问题在于,在有AOP代理的时候,会存在有一部分是使用的不是代理对象,一部分使用的是代理对象,这个就不对了。
- 如果使用两级缓存,代理对象提前手动暴露,是不是就可以了? 没有自己证明是否可以的,但是从理论上来说是可以的,如果手动提前暴露代理对象,去掉三级缓存的话,实际上是可以的,但是使用代理对象这种情况,比起直接的循环依赖这种情况,要少很多,如果去掉三级缓存,则每个对象都需要走一遍这个提前暴露代理对象的流程,效率就相对来说会低一些。
问题3:什么场景下不能解决循环依赖问题。
Spring解决的是Setter注入,单例模式下的循环依赖问题,原型模式和构造器注入的方式下,无法解决,原因也很好理解,原型模式每次都是生成新的对象,无法判断对象是否存在,这种会抛出一个异常BeanCurrentlyInCreationException,构造器注入这种,实例化和初始化必须同时完成,因此也无法解决循环依赖,当依赖注入的方式不能全是构造器注入的方式,也是可以使用构造器注入来解决循环依赖的。
注:Spring在创建Bean时默认会根据自然排序进行创建,所以A会先于B进行创建
详细的可以参考:高频面试题:Spring 如何解决循环依赖? 和 面试必杀技,讲一讲Spring中的循环依赖
17、Spring中事务的隔离级别
4种事务的隔离级别,跟数据库的隔离级别是一样的,可以参考MySQL的隔离级别。
ISOLATION_READ_UNCOMMITTED
:读未提交,可以读取没有commit的数据,解决了更新丢失的问题,但是容易产生脏读数据、幻读数据和不可重复读。
ISOLATION_READ_COMMITTED
:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ
:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生,MySQL里面通过next-key lock和间隙锁可以在一定程度上解决幻读问题。
ISOLATION_SERIALIZABLE
:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
注:Spring本身是可以设置事务的隔离级别的,当Spring设置的隔离级别和数据库的隔离级别不一样时,以Spring设置的为准,当然前提是Spring设置的隔离级别数据库是支持的,如果不支持的话,以数据库为准。
18、Spring中事务的管理方式
编程式事务和声明式事务两种,一般编程式用的少,大部分都是直接使用声明式事务。
19、Spring中事务的传播特性
PROPAGATION_REQUIRED
:如果当前存在事务,则加入到当前事务中,如果不存在,则创建一个新的事务。PROPAGATION_REQUIRED_NEW
: 如果当前存在事务,则将事务挂起,创建一个新的事务;PROPAGATION_SUPPORTS
: 如果当前存在事务,则加入到当前事务里面,如果不支持,则以非事务的方式执行。PROPAGATION_NOT_SUPPORTED
: 如果存在事务,则挂起事务,以非事务的方式运行。PROPAGATION_MANDATORY
: 以事务的方式运行,如果不存在事务,则抛出异常。PROPAGATION_NEVER
: 以非事务方式运行,如果当前存在事务,则抛出异常。PROPAGATION_NESTED
: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED
。
具体的例子,大家可以看这篇文章,这篇文章写得很好:Spring事务
20、Spring事务失效的原因
这个地方可以结合这个AOP代理来理解:
- 底层的数据库引擎不支持事务,只有innodb才能使用事务
- 没有将Bean注入到Spring容器中,Bean不能被容器管理,因此就无法使用代理。
- 发生了内部自己调用,这种是不生效的,原因就是以为AOP代理,内部调用直接使用的是this指针,没有走Spring的代理。
- 发生异常,但是异常被捕获了,并没有抛出来,这种情况下,外部的事务,是无法回滚的。
- 异常虽然抛出来了,但是没有设置回滚异常,默认是RuntimeException异常,可以通过rollFor参数设置异常捕获的类型。
- 使用事务的方法,不是public类型,不能被代理,protected和package都不能执行事务,static、final都不行,因为CGLIB代理时,需要创建一个可以被重写的子类。
21、Spring MVC中URL的访问过程
SpringMVC
执行流程:
- 用户发送请求至前端控制器
DispatcherServlet
DispatcherServlet
收到请求调用处理器映射器HandlerMapping
。- 处理器映射器根据请求url找到具体的处理器,生成处理器执行链
HandlerExecutionChain
(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet
。 DispatcherServlet
根据处理器Handler
获取处理器适配器HandlerAdapter
执行HandlerAdapter
处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作- 执行处理器
Handler
(Controller
,也叫页面控制器)。 Handler
执行完成返回ModelAndView
HandlerAdapter
将Handler
执行结果ModelAndView
返回到DispatcherServlet
DispatcherServlet
将ModelAndView
传给ViewReslover
视图解析器ViewReslover
解析后返回具体View
DispatcherServlet
对View
进行渲染视图(即将模型数据model
填充至视图中)。DispatcherServlet
响应用户。
22、Spring MVC中的9大组件
这个可以看一下Spring MVC
的源码,在DispatchServlet
的Javadoc里面,有写:
HandlerMapping
:处理器映射器,根据用户的请求url来查找Handler。HandlerAdapter
:适配器。Handler处理器可能有多种,我们常用的就是@RequestMapping这种,这种是基于注解的RequestMappingHandlerAdapter,还有一种是实现Controller接口的方式,对应的适配器为SimpleControllerHandlerAdapter,可以通过重写里面的Controller接口的handleRequest方法,来实现处理器,第三种就是SimpleServletHandlerAdapter,用于处理Servlet的一些请求。HandlerExceptionResolver
:处理异常ViewResolver
:视图解析器,这个其实现在用的会少一些了,现在一般都是前后端分离,一般不走这个视图解析器。RequestToViewNameTranslator
: 专门处理没有返回值,具体的不太清楚LocaleResolver
:国际化转换ThemeResolver
:主题MultipartResolver
: 文件上传FlashMapManager
:(汗)没有用过
其实主要了解核心的几个,HandlerMapping、HandlerAdapter、MultipartResolver的用途和使用方式就够了,其他的简单知道就好了。
总结
这段时间,看了一些Spring相关的文档和源码,目前整理了有22个题目,都是一些常见的面试题,后续碰都会继续的更新这个文档,帮助自己记录Spring相关的知识,同时也给小伙伴们一起交流探讨Spring中的一些面试题,大家可以将碰到的面试题在下方留言,大家一起交流。希望对小伙伴能有所帮助,另外如有问题,希望指点一二,感谢大家。