Spring系列面试内容(常问点)
写在前面
- 博客首页
- 内容由思维导图转换而来
Spring
IOC
讲讲什么是IOC?
通过IOC容器来控制Bean对象的产生,在Spring之前要得到所依赖的对象,就得自己一个一个new(正转),有了IOC就是他帮我找一个,也就是把所需要依赖的对象通过IOC容器注入进来,反转的是去所依赖的对象
SpringBean
依赖注入(DI)注入Bean的方法
- @Autowired
- @Resource
- @Inject
告诉IOC容器我是一个Bean
XML
注解:@Bean @Component(不知道那一层直接用) @Service @Dao @Controller
Bean生命周期
spring容器帮助我们去管理对象,从对象的产生到销毁的环节都由容器来控制,关键环节就是 实例化 和 初始化 以及 扩展点
1. 实例化bean对象,通过反射的方式来生成,在源码中有一个createBeanInstance的方法专门生成对象
2. 创建完bean后,其属性值都是默认值,需要对bean填充属性,通过populateBean方法来完成
3. 向bean对象中设置容器相关属性,invokeAwareMethods方法将容器对象设置到具体的bean对象中。(这里就是要检查是否实现了Aware先骨干的接口)
4. 调用BeanPostProcessor中的前置处理方法来进行bean对象的扩展工作(这是一个扩展点,可以实现自己的前置操作)
5. 调用invokeInitMethods方法来完成初始化方法的调用,如果bean对象实现了initializingBean接口,如果实现了调用afterPropertiesset方法设置
6. 调用BeanPostProcessor后置处理工作,AOP就是在此处实现的。
7. 获取到完成对象,通过getBean的方式进行对象的获取和使用
8. 等待容器关闭时,先判断是否实现DisposableBean接口,再调用destroyMethod方法
Bean创建过程中循环依赖
问题描述:当A中有B,B中有A的时候,Spring是如何去实例化,来防止循环依赖的?为什么constructer循环依赖无法解决,而setter可以?为什么单例模式bean可以而prototype类型不可以?
三级缓存
一级缓存:singletonObjects,存放成熟的对象,也就是初始化成功完成后的对象
- 二级缓存:earlySingletonObjects,存放已经实例化,但是为进行初始化复制的半成品对象
- 三级缓存:singletonFactories,存放的是一个函数式接口ObjectFactory
- 解决循环依赖,主要就是利用Spring的二三级缓存,提前暴露未完全创建成功的单例对象来实现举个例子:A与B互相依赖,A先进行实例化,实例化后其实已经可以找到当前实例化后的对象,将A放入三级缓存singletonFactories,然后去进行初始化,等需要初始化内部属性B时,会依次去找一二三级缓存,发现没有就会create创建B,等B在依赖A时,也会去找一二三级缓存,发现一二级没有,就会再去第三级缓存中是否存在A,有就就会取出放入二级缓存,然后B走完整个流程,将自己放入一级缓存SingletonFactories,然后A就可以找到B,并且是完整的B,走完剩下的创建流程。不能解决构造函注入方法的循环依赖,在调用构造函数之前,未将其缓存到第三级缓存中 2. 不能解决prototype作用域下的循环依赖,3.不能解决多例模式下的循环依赖,这些情况下的循环依赖,可以通过@lazy延迟加载,@DependOn()指定先后加载顺序来解决
为什么是三级缓存?
不是一级的原因:因为如果只有一层的话,那么就会把半成品对象和成品对象混在一起,而半成品对象是不能暴露给外部访问的
- 只有两级?如果整个应用不涉及AOP的话,两级就可以,但是如果存在AOP循环依赖那么就不行
- 三级解决问题:因为三级缓存缓存中,缓存的是ObjectFactory,其提供了getObject方法,存在代理时保证了只创建一个同名的Bean,基于这种设计,没有发生循环依赖的bean就是正常的创建流程,有相互引用的bean会触发getEarlyBeanReference。
作用域
- singleton单例,就存在一个实例对象
- prototye,每次调用getBean都会创建新的bean返回
- request,每次http请求都会创建一个bean
- session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean
- application:限定一个Bean的作用域为ServletContext的生命周期
AOP
前置基础
反射
- 可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性
- 具体操作,是通过全类名获取类,然后取其方法,最后通过method.invoke()执行
静态代理
- 没创建一个被代理类,就得创建一个代理类,因为继承自同一个接口,实现同一个方法,本质上代理类的对象作为了代理类的依赖对象
动态代理
JDK
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler
);
}
}
public class DebugInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;
public DebugInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return result;
}
}需要像静态代理那样,写出代理类实现InvocationHandler接口,实现invoke方法,内部调用通过method.invoke()反射实现
必须要有接口,本质上代理是调用了Proxy.newProxyInstance()
CGLIB
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println('before method ' + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println('after method ' + method.getName());
return object;
}
}
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}关键实现:Enhancer + MethodInterceptor,需要继承MethodInterceptor,重写intercept方法,内部通过MethodProxy.invokeSuper()实现
需要有继承,本质上代理通过Enhancer创建动态代理增强类
通知:前置,后置,环绕,事后返回通知和异常通知
Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理,就是在代理对象方法反射调用前后进行一些逻辑处理(前置,后置,环绕的理解)
不能增强的类
- Spring AOP只能对IoC容器中的Bean进行增强,对于不受容器管理的对象不能增强。
- 由于CGLib采用动态创建子类的方式生成代理对象,所以不能对final修饰的类进行代理。
事务
事务隔离级别和数据库一样,但是如果Spring设置的隔离级别和Mysql的隔离级别不一样,采用Spring配置的隔离级别
@Transactional 注解
这是一个声明式业务,本地底层通过AOP代理增强实现
事务失效情况
- 类内部调用:类Test中有A,B方法,A无B有,A调用B,其外部调用A方法时,B的事务也不生效
- 非Public修饰(底层源码中,会判断修饰符是不是public)
- rollbackfor:异常不匹配,Spring默认处理运行时异常,即只有抛出运行时异常时,才会触发事务回滚,否则并不会回滚
- @Transactional 注解属性 propagation 设置错误
事务传播:A内调用B(后面参数修饰被调用者B)
- REQUIRED(默认):A有B加入,A无B新建
- REQUIRES_NEW:A有事务,B会重新开启一个事务(与A事务之间独立,各自回滚不影响对方)
- NESTED:A有事务,B会生成一个嵌套事务(嵌套在A事务中,比如A回滚,那么B也是需要回滚,而B异常回滚,如果A捕获其异常,可以只做B的回滚)
- support:A有B加入,A无B无
- not_support:以非事务方式执行
- MANDATORY:必须要有,A有B加入,不然抛出异常
- NEVER:不允许有事务,不然抛出异常
BeanFactory和FactoryBean的区别
- BeanFactory:Spring容器最核心也是最基础的接口,本质是一个工厂类,管理Bean的工厂,一般使用其子接口
- FactoryBean:工厂Bean,实现该接口可以定义索要创建bean实例,只需要实现其getObject方法
SpringMvc
MVC架构
- Model:处理应用程序数据逻辑的部分
- View:负责数据显示部分
- Controller:负责具体过来的请求处理,就是用户交互
- Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。
执行流程
- 当一个请求过来,首先会被DispatcherServelet拦截住,请求多个或一个HandlerMapping,返回一个执行链
- 2.DispatcherServelet将执行链中包含的Handler交给HandlerAdapter包装然后找到真正需要执行的Handle(也就是Controller)
- 3.处理完handle的逻辑后,返回一个ModelAndView(包含模型和视图),DispatcherServlet收到后,会请求ViewResolver去解析视图
- 4.ViewResolver根据view的内容进行解析后,回传给DispatcherServlet,进行视图渲染,将Model中的数据填充到view中,返回到具体的View视图(JSP,FreeMarker),最后用户可以在浏览器看到
Interceptor拦截器实现原理
核心接口就是HandlerInterceptor
本质上是利用了AOP的原理,就是动态代理+反射实现,进入到Controller前进行一些处理,然后执行完页面选然后,再进行一些处理
先执行preHandle()方法,后执行postHandle()方法,调用是在DispatcherServerlet核心方法doDispatcher()中
源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 获取可以执行当前Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = 'GET'.equals(method);
if (isGet || 'HEAD'.equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug('Last-Modified value for [' + getRequestUri(request) + '] is: ' + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 执行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
SpringBoot
自动装配原理
@SpringBootApplication
@SpringBootConfiguration:表示当前是注解类,标注当前类是一个Java Config类,会被扫描加载到IOC容器
@EnableAutoConfiguration:开启springboot的注解功能
- @AutoConfigurationPackage
- @Import({AutoConfigurationImportSelector.class}):借助AutoConfigurationImportSelector,将SpringBoot应用将所有符合条件(spring.factories)的bean定义(如Java Config@Configuration配置)都加载到当前SpringBoot创建并使用的IoC容器,底层其实是通过SpringFactoriesLoader.loadFactoryNames()这个方法,传入factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类。
@ComponentScan:扫描路径设置,默认扫描@SBA根目录下的所有包
自启动原理
SpringApplication.run(RuoYiApplication.class, args);
构造函数,初始化一些关键属性,比如设置ApplicationContextInitializer和一些listener
run方法:整个执行流程(重要部分),大致流程就是EventPublishingRunListener中事件发布的流程
- 发布SprintBoot开始启动事件(EventPublishingRunListener#starting())
- 创建配置environment(EnvironmentPrepared())
- 创建对应的ApplicationContext,一般都是Web类型
- 完成ApplicationContext的一个初始化流程,从contextPrepared()到contextLoaded(),中间会有一系列的配置,重点的是调用AbstractApplicationContext的refresh()方法,也就是完成容器的创建,和自动装配
- afterRefesh –> 发布SpringBoot程序已启动事件(started())started –> 最后发布就绪事件ApplicationReadyEventrunning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56public ConfigurableApplicationContext run(String... args) {
// 创建一个StopWatch实例,用来记录SpringBoot的启动时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 发布SprintBoot启动事件:ApplicationStartingEvent
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印SpringBoot的banner和版本
Banner printedBanner = printBanner(environment);
// 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
// 打印启动日志,打印profile信息(如dev, test, prod)
// 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
// 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
refreshContext(context);
// hook方法
afterRefresh(context, applicationArguments);
// stopWatch停止计时,日志打印总共启动的时间
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// 发布SpringBoot程序已启动事件ApplicationStartedEvent
listeners.started(context);
// 调用ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}