Spring AOP 执行顺序 && Spring 循环依赖(面试必问)
一、Spring AOP 执行顺序
1、AOP 常用注解
@Before
前置通知:目标方法之前执行
@After
后置通知:目标方法之后执行(始终执行)
@AfterReturning
返回通知:执行方法结束前执行(异常不执行)
@AfterThrowing
异常通知:出现异常的时候执行
@Around
环绕通知:环绕目标方法执行
小知识:
SpringBoot1.X 版本对应 Spring4
SpringBoot 2.X 版本对应 Spring5
在 Spring4 版本下 AOP 的执行顺序:
正常情况:
@Before -------业务代码 -------- @After --------- @AfterReturning
加上环绕通知,环绕通知一半在 @Before 之前执行,一半在 @After 之前执行
异常情况:
@Before -------业务代码 -------- @After --------- @AfterThrowing
加上环绕通知,异常情况下环绕通知会执行一半
在 Spring5 版本下 AOP 的执行顺序:
正常情况:
@Before -------业务代码 -------- @AfterReturning --------- @After
加上环绕通知,环绕通知一半在 @Before 之前执行,一半在 @After 之后执行
异常情况:
@Before -------业务代码 -------- @AfterThrowing --------- @After
加上环绕通知,异常情况下环绕通知会执行一半
二、Spring 循环依赖
1、什么是循环依赖?
多个 bean 之间相互依赖,形成一个闭环。比如:A 依赖于 B,B 依赖于 C ,C 依赖于 A。
2、构造方法注入和 setter 方法注入异同?
1、其实循环依赖很好理解,Spring 容器初始化会去创建 bean 。你 bean 中注入了属性,比如 A 这个类中你注入了 B 这个类,那么要创建 A 这个 bean ,就必须要 B 这个 bean 存在,然后你又在 B 这个类中注入了 A 这个类,同样,要创建 B 这个类,首先需要 A 这个 bean 存在,最后的结果就是异常。
2、根据 Spring 官网的描述,循环依赖对构造方法注入不友好,对 setter 方法注入比较友好。
3、我们 AB 循环依赖问题只要 A 的注入方式是 setter 且 singleton ,就不会有循环依赖问题
小结论:
Spring 容器中,默认的单例(singleton)场景是支持循环依赖的,不会报错。
Spring 容器中,原型(prototype)的场景是不支持循环依赖的,会报错。
3、Spring 是怎么解决循环依赖的?
Spring 内部通过 3 级缓存来解决循环依赖。
第一级缓存:singletonObjects
,存放已经经历完整生命周期的 Bean 对象
第二级缓存:earlySingletonObjects
,存放早期暴露出来的 Bean 对象,Bean 的生命周期未结束(属性还未填充完成),说人话就是 bean 已经创建了,但是属性还没有初始化。类似于房子买好了,但是家具还没有搬进来。
第三级缓存:singletonFactories
存放可以生成 Bean 的工厂。
只有单例的 bean 会通过三级缓存提前暴露来解决循环依赖问题,而非单例的 bean ,每次从容器中获取的都是一个新的对象,都会重新创建,所以非单例的 bean 是没有缓存的,不会将其放到三级缓存中。
实例化和初始化的概念:
实例化:内存中申请一块内存空间(类似于买好房子,但是家具什么的还没有搬进去)
初始化属性填充:完成属性的各种赋值(类似于装修,家具什么的搬进去)
A/B 两个对象在三级缓存中的迁移说明:
1、A 创建过程需要 B ,于是 A 将自己放到三级缓存中,去实例化 B
2、B 实例化的时候发现需要 A,于是 B 先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了 A,然后把三级缓存里面的 A 放到二级缓存里面,并删除三级缓存里面的 A
3、B 顺利初始化完毕,将自己放到一级缓存里面(此时 B 中的 A 依然是创建中状态),然后回来接着创建 A,此时 B 已经创建结束,直接冲一级缓存中拿到 B,然后完成创建,并将 A 自己放到一级缓存里面。
Spring 解决循环依赖:
Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化。
每次创建 bean 之前,我们都会从缓存中查一下有没有该 bean,因为是单例,只能有 1 个。
当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象的属性了,这时候发现依赖了 beanB,接着就去创建 beanB,同样的流程,创建完 beanB ,填充属性时又发现它依赖了 beanA,又是同样的流程。
不同的是:
这时候可以在三级缓存中查到刚放进去的原始对象 beanA,所以不需要继续创建,用它注入 beanB,完成 beanB 的创建。
既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。
注:
Spring 解决循环依赖依靠的是 Bean 的 "中间态" 这个概念,而这个中间态指的是已经实例化但还没有初始化的状态 (半成品)。实例化的过程又是通过构造器创建的,如果 A 还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。
更多有意思的内容可以关注我的视频号:
版权声明: 本文为 InfoQ 作者【hepingfly】的原创文章。
原文链接:【http://xie.infoq.cn/article/b2bd6dd78c0f857c4837c94ca】。文章转载请联系作者。
评论