写点什么

Spring AOP 执行顺序 && Spring 循环依赖(面试必问)

用户头像
hepingfly
关注
发布于: 2021 年 03 月 15 日
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 还没创建好出来怎么可能提前曝光,所以构造器的循环依赖无法解决。


更多有意思的内容可以关注我的视频号:



发布于: 2021 年 03 月 15 日阅读数: 46
用户头像

hepingfly

关注

视频号:hepingfly 分享干货,欢迎关注~ 2018.06.23 加入

B站程序员。目标是做一个有才华,身上有光的男人。

评论

发布
暂无评论
Spring AOP 执行顺序 && Spring循环依赖(面试必问)