• spring探秘之循环依赖的解决(一):理论基石


    1. 什么是循环依赖

    spring 在依赖注入时,可能会出现相互注入的情况:

    1. @Service
    2. public class Service1 {
    3. @Autowired
    4. private Service2 service2;
    5. }
    6. @Service
    7. public class Service2 {
    8. @Autowired
    9. private Service1 service1;
    10. }
    11. 复制代码

    如以上代码,在Service1 中通过@Autowird注入了Service2,在Service2 中通过@Autowird注入了Service1,这种相互注入的情况,就叫做循环依赖。

    2. 循环依赖会有什么问题

    实际上,这种A持有B对象,B也持有A对象的情况,java代码是完全支持的:

    1. /**
    2. * 准备service1
    3. */
    4. public class Service1 {
    5. private Service2 service2;
    6. public void setService2(Service2 service2) {
    7. this.service2 = service2;
    8. }
    9. public Service2 getService2() {
    10. return this.service2;
    11. }
    12. }
    13. /**
    14. * 准备service2
    15. */
    16. public class Service2 {
    17. private Service1 service1;
    18. public void setService1(Service1 service1) {
    19. this.service1 = service1;
    20. }
    21. public Service1 getService1() {
    22. return this.service1;
    23. }
    24. }
    25. /**
    26. * 主方法中调用
    27. */
    28. public class Main {
    29. public void main(String[] args) {
    30. // 准备两个对象
    31. Service1 service1 = new Service1();
    32. Service2 service2 = new Service2();
    33. // 相互设置
    34. service1.setService2(service2);
    35. service2.setService1(service1);
    36. }
    37. }
    38. 复制代码

    那么,在spring中,两个类相互注入对方实例的情况,会有什么问题呢?我们来看spring bean的创建过程(注意:这里我们仅分析beanscopesingleton的情况,也就是scope单例的情况):

    这个过程中有几点需要说明下:

    1. 创建对象:这个其实就是使用jdk提供的反射机制创建java对象,以第1节提到的Service1为例,可简单理解为Service1 service = new Service1()
    2. 注入依赖对象:还是以第1节提到的Service1为例,Service1中通过@Autowired自动注入Service2,这一步就是给Service2赋值的过程,可简单理解为service1.setService2(service2)
    3. singletonObjects:经过上面两步后,一个java对象就变成了一个spring bean,然后保存到singletonObjects了,这是个mapkey是bean的名称,value是bean,它只保存spring bean,不会只在java实例。

    实际上,java对象变成spring bean,不仅仅只是依赖注入,还有初始化、执行beanPorcessor方法等,由于本文是分析spring bean的循环依赖的,因此我们重点关注与循环依赖相关的步骤。

    2.1 循环依赖产生的问题

    了解了spring bean的产生过程之后,接下来我们就来分析下循环依赖产生的问题,在正式分析前,我们先来明确两个概念:

    • java对象:实际上,java中一切对象都可以称之为java对象,为了说明方便,以下提到的java对象仅指实例化完成、但未进行spring bean的生命周期对象;
    • spring bean:是一个java对象,并且进行了完整的spring bean的生命周期对象;

    spring bean的创建过程如下:

    对上图说明如下:

    1. service1对象创建完成后,spring发现service1需要注入service2,然后就去singletonObjects中查找service2,此时是找不到service2,然后就开始了service2的创建过程;
    2. service2对象创建完成后,spring发现service2需要注入service1,然后就去singletonObjects中查找service1,此时是找不到service1,因为第一步中service1并没有创建成功 ,然后就开始了service1的创建过程;
    3. 流程跳回到1,再次开始了service1的创建、属性注入过程。

    到这里,我们惊喜地发现,循环出现了!

    2.2 引入earlySingletonObjects解决循环依赖

    我们分析下,循环出现的原因在于,在service2获取service1时,由于singletonObjects中此时并不存在service1,因此会再走service1的创建过程,重新创建service1,因此,我们有个大胆的想法:如果在service1实例化后就把它保存起来,后面再再找service1时,就返回这个未进行依赖注入的service1,像下面这样:

    上图中,引入了earlySingletonObjects,这也是个map,同singletonObjects一样,key是bean的名称,value 是一个未完成依赖注入的对象。

    对上图说明如下:

    1. service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入;
    2. service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程;
    3. service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入;
    4. service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,此时service2就是一个spring bean了,将其保存到singletonObjects中;
    5. 经过第4步后,我们得到了service2,然后将其注入到service1中,此时service1也成了一个spring bean,将其保存到singletonObjects中。

    经过以上步骤,我们发现,循环依赖得到了解决。

    2.2 aop下的循环依赖

    经过上面的分析,我们发现只要额外引入一个earlySingletonObjects后,循环依赖就能得到解决。但是,循环依赖真的得到了解决吗?spring除了ioc外,还有另一个重大功能:aop,我们来看看aop情况下出现循环依赖会怎样。

    1. aop对象的创建过程

    在正式介绍aop下的循环依赖前,我们先来明确两个个概念:

    • 原始对象:区别于代理对象,指未进行过aop的对象,可以是java对象,也可以是未进行aop的spring bean;
    • 代理对象:进行过aop的对象,可以是java对象仅进行过aop得到的对象(仅进行过aop,未进行依赖注入,也未进行初始化),也可以是进行过aop的spring bean.

    我们先来看看aop是如何创建对象的:

    相比于2.1中的流程,aop多了"生成代理对象"的操作,并且最终保存到singletonObjects中的对象也是代理对象。

    原始对象与代理对象之间是什么关系呢?用代码示意下,大致如下:

    1. public class ProxyObj extends Obj {
    2. // 原始对象
    3. private Obj obj;
    4. ...
    5. }
    6. 复制代码

    实际上,两者之间的关系并没有这么简单,但为了说明问题,这里对两者关系做了简化,小伙伴们只需要明白,代理对象持有原始对象的引用即可。

    关于原始对象如何变成代理对象的,可以参考spring aop 之 AnnotationAwareAspectJAutoProxyCreator 分析(下)

    对以上创建过程,用java代码模拟如下:

    1. /**
    2. * 准备一个类
    3. */
    4. public class Obj1 {
    5. }
    6. /**
    7. * 准备一个类,内部有一个属性 Obj1
    8. */
    9. public class Obj2 {
    10. private Obj1 obj1;
    11. // 省略其他方法
    12. ...
    13. }
    14. /**
    15. * 准备Obj2的代理类,内部持有obj2的对象
    16. */
    17. public class ProxyObj2 extends Obj2 {
    18. private Obj2 obj2;
    19. public ProxyObj2(Obj2 obj2) {
    20. this.obj2 = obj2;
    21. }
    22. // 省略其他方法
    23. ...
    24. }
    25. 复制代码

    接着,就是模拟“创建-->属性注入-->生成代理对象-->保存到容器中”的 流程了:

    1. public static main(String[] args) {
    2. // 准备一个容器,这里保存的是完成上述生命周期的对象
    3. // 1. 如果元素是原始对象,则该对象已经完成了属性注入
    4. // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
    5. Collection<?> collection = new ArrayList();
    6. // 开始 Obj2 的创建流程
    7. // 1. 创建 Obj2 对象
    8. Obj2 obj2 = new Obj2();
    9. // 2. 往 Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2
    10. Obj1 obj1 = new Obj1();
    11. obj2.setObj1(obj1);
    12. // 3. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
    13. ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
    14. // 4. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
    15. collection.add(proxyObj2);
    16. }
    17. 复制代码

    上述代码中,

    • new Obj2()模拟对象的创建
    • obj2.setObj1(xxx)模拟依赖注入
    • new ProxyObj2(xxx)模拟代理对象的生成
    • collection.add(xxx)模拟对象添加到容器中的过程

    模拟的流程如下:

    1. 创建obj2对象
    2. Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2
    3. 生成Obj2的代理对象proxyObj2proxyObj2中持有 Obj2的原始对象
    4. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时

    仔细分析上面的步骤,就会发现,上面的第2步与第3步完全调换顺序也没问题,代码模拟如下:

    1. public static main(String[] args) {
    2. // 准备一个容器,这里保存的是完成上述生命周期的对象
    3. // 1. 如果元素是原始对象,则该对象已经完成了属性注入
    4. // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
    5. Collection<?> collection = new ArrayList();
    6. // 开始 Obj2 的创建流程
    7. // 1. 创建 Obj2 对象
    8. Obj2 obj2 = new Obj2();
    9. // 2. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
    10. ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
    11. // 3. 往 obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2
    12. Obj1 obj1 = new Obj1();
    13. // 这里是注入到原始对象中
    14. obj2.setObj1(obj1);
    15. // 4. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时
    16. collection.add(proxyObj2);
    17. }
    18. 复制代码

    上述代码的流程如下:

    1. 创建obj2对象
    2. 生成Obj2的代理对象,代理对象中持有 Obj2的原始对象
    3. 往 Obj2 中注入 obj1,但此时并没有obj1,因此先要创建obj1,再将其注入到Obj2
    4. proxyObj2已经走完了完整的生命周期,因此将代理对象添加到容器时

    从代码上看,proxyObj2(代理对象)中持有ob2(原始对象),生成代理对象后,继续对原始对象进行属性注入,依然能影响代理对象,最终代理对象持有的原始对象也完成了依赖注入,整个过程用图形示意如下:

    这里我们再次申明,从java对象到spring bean的步骤有好多,这里我们仅关注与循环依赖相关的步骤,如果想了解spring bean详细的初始化过程,可查看 spring启动流程之启动流程概览

    到这里,我们探索到代理对象的生命周期可以有两种:

    • 创建-->属性注入-->生成代理对象-->将代理对象保存到容器中
    • 创建(原始对象)-->生成代理对象(提前进行aop)-->对原始对象进行属性注入-->将代理对象保存到容器中

    这两种都能达到最终目的,即保存到容器中的是代理对象,且代理对象对应的原始对象完成了依赖注入。请牢记这两个创建流程,这是后面解决aop下循环依赖问题的核心,说白了,aop下的循环依赖问题之所以能解决,就是因为对象可以提前进行aop操作

    2. 为什么用earlySingletonObjects无法解决循环依赖?

    前面我们主要说明了代理对象的创建过程,接下来我们来看看在aop下,使用earlySingletonObjects来解决循环依赖有什么问题:

    我们来分析上图的流程:

    1. service1对象创建完成后,先将service1放入earlySingletonObjects,然后进行依赖注入;
    2. service1进行依赖注入时,spring发现service1需要注入service2,然后先去earlySingletonObjects查找service2,未找到;再去singletonObjects中查找service2,还是未找到,于是就开始了service2的创建过程;
    3. service2对象创建完成后,先将service2放入earlySingletonObjects,然后进行依赖注入;
    4. service2进行依赖注入时,spring发现service2需要注入service1,然后就去earlySingletonObjects查找service1,找到了,就将service1注入到service2中,然后再进行aop,此时service2是一个代理对象,将其保存到singletonObjects中;
    5. 经过第4步后,我们得到了service2的代理对象,然后将其注入到service1中,接着再对service1进行aop,此时service1也成了一个spring bean,将其保存到singletonObjects中。

    上述步骤有什么问题呢?仔细看第4步,就会发现,注入到service2service1并不是代理对象!纵观全局,最终得到的service1service2都是代理对象,注入到service2service1应该也是代理对象才对。因此,在aop下,循环依赖的问题又出现了!

    2.3 spring 的解决方案

    前面我们提到,在aop下,引入earlySingletonObjects并不能解决循环依赖的问题,那spring是怎么解决的呢?spring再次引入了一个map来解决这个问题,这也是人们常说的spring三级缓存,对这三个map说明如下:

    • 一级缓存singletonObjects:类型为ConcurrentHashMap,位于DefaultSingletonBeanRegistry类中,keybeanNamevalue是完整的spring bean,即完成属性注入、初始化的bean,如果bean需要aop,存储的就是代理对象;
    • 二级缓存earlySingletonObjects:类型为HashMap,位于DefaultSingletonBeanRegistry类中,keybeanNamevalue是实例化完成,但未进行依赖注入的bean,如果bean需要aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入;
    • 三级缓存singletonFactories:类型为HashMap,位于DefaultSingletonBeanRegistry类中,keybeanNamevalue存储的是一个lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean)getEarlyBeanReference中的bean是刚创建完成的java bean,没有进行spring依赖注入,也没进行aop(关于这个lambda表达式,后面会继续分析)。

    为了说明方便,下面对singletonObjectsearlySingletonObjectssingletonFactories分别称为一级缓存二级缓存三级缓存

    spring解决aop下的循环依赖流程如下:

    这个图看着比较复杂,其实分开来看就比较简单了,上述操作中,1~8是获取service1的流程,5.1~5.8是获取service2的流程,5.5.1是再次获取service1的流程,只不过在处理service1的初始化过程中,会触发service2的初始化过程,而service2的初始化时,又会依赖到service1,因此才看着像是连在一起,比较复杂。

    对上图的过程,这里说明如下(建议:如果觉得流程比较复杂,可以先看1~8的操作,再看5.1~5.8的操作,最后两者联合起来看,这样会清晰很多):

      1. service1:获取service1,从一级缓存中获取,此时是获取不到的;
      1. service1:创建service1的实例;
      1. service1:获取需要注入的属性与方法(在原始对象上进行获取);
      1. service1:如果开启了支持循环依赖的配置,就将service1放到三级缓存中(是否支持循环依赖,是可以配置的);
      1. service1:对service1进行依赖注入,需要service2,然后就开始了service2的获取流程;
    • 5.1 service2:获取service2,从一级缓存中获取,此时是获取不到的;
    • 5.2 service2:创建service2的实例;
    • 5.3 service2:获取需要注入的属性与方法(在原始对象上进行获取);
    • 5.4 service2:如果开启了支持循环依赖的配置,就将service2放到三级缓存中(是否支持循环依赖,是可以配置的);
    • 5.5 service2:对service2进行依赖注入,需要service1,然后就开始了service1的获取流程;
    • 5.5.1 service1: 获取service1,从一级缓存中获取,获取不到;此时发现service1正在创建中,于是继续从二、三级缓存中获取,最终从三级缓存中获取到了,将其放入二级缓存。从三级缓存获取的过程中,会判断service1是否需要进行aop,然后开始aop操作,因此放入二级缓存中的是service1代理代理,提前进行aop是解决循环依赖的关键;
    • 5.6 service2:得到了service1后(这里的service1是代理对象),将其注入到service2中,接着对service2进行aop,得到service2的代理对象;
    • 5.7 service2:如果支持循环依赖,先从一、二级缓存中再次获取service2,都未获取到,就使用当前service2(当前service2是代理对象);
    • 5.8 service2:将service2的代理对象放入一级缓存中,删除二、三级缓存,至此,service2初始化完成,注入的service1是代理对象,一级缓存中的service2也是代理对象;
      1. service1:回到service1的生命周期,拿到service2(这里的service2是代理对象)后,将其注入到service1service1的依赖注入完成,进行初始化,这里会判断service1是否需要进行aop,虽然service1是需要进行aop的,但由于在5.5.1已经进行过aop了,因此,这里直接返回(到这一步,service1还是原始对象);
      1. service1:如果支持循环依赖,先从一级缓存中获取service1,获取不到;再从二缓存中获取service1,可以获取到(从5.5.1可知,二级缓存里是service1代理对象),返回;
      1. service1:将二级缓存中获取的对象注册到一级缓存中,删除二、三级缓存,至此,service1初始化完成,注入的service2是代理对象,一级缓存中的service1也是代理对象。

    以上流程,虽然步骤较多,但service1service2的获取步骤是相同的,只要弄清了其中之一的获取流程,另一个bean的获取流程就很雷同了。

    在上述流程中,还有两个数据结构需要说明下:

    • singletonsCurrentlyInCreation:类型为SetFromMap,位于DefaultSingletonBeanRegistry,创建方式为 Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由ConcurrentHashMap实现的set,存储的是正在创建中的对象,判断当前对象是否在创建中就是通过查找当前对象是否在这个set中做到的;
    • earlyProxyReferences:类型为ConcurrentHashMap,位于AbstractAutoProxyCreator,存储的是提前进行aop的对象,如果一个对象提前进行了aop,在后面再次aop时,会通过判断对象是否在earlyProxyReferences中而确定要不要进行aop,以此来保证每个对象只进行一次aop

    至此,spring一共提供了5个数据结构来辅助解决循环依赖问题,总结如下:

    结构说明
    singletonObjects一级缓存,类型为ConcurrentHashMap,位于DefaultSingletonBeanRegistry类中,keybeanNamevalue是完整的spring bean,即完成属性注入、初始化的bean,如果bean需要aop,存储的就是代理对象
    earlySingletonObjects二级缓存,类型为HashMap,位于DefaultSingletonBeanRegistry类中,keybeanNamevalue是实例化完成,但未进行依赖注入的bean如果bean需要aop,这里存储的就是代理对象,只不过代理对象所持有的原始对象并未进行依赖注入
    singletonFactories三级缓存,类型为HashMap,位于DefaultSingletonBeanRegistry类中,keybeanNamevalue存储的是一个lambda表达式:() -> getEarlyBeanReference(beanName, mbd, bean)getEarlyBeanReference(xxx)中的bean是刚创建完成的java bean,没有进行spring依赖注入,也没进行aop
    singletonsCurrentlyInCreation类型为SetFromMap,位于DefaultSingletonBeanRegistry,创建方式为 Collections.newSetFromMap(new ConcurrentHashMap<>(16)),表明这是个由ConcurrentHashMap实现的set,存储的是正在创建中的对象,可以用来判断当前对象是否在创建中
    earlyProxyReferences类型为ConcurrentHashMap,位于AbstractAutoProxyCreator,存储的是提前进行aop的对象,可以用来判断bean是否进行过aop,保证每个对象只进行一次aop

    以上就是spring 解决循环依赖的完整流程了。

    3. 代码模拟

    在正式分析源码前,我们首先模拟循环下依赖解决的过程,代码如下:

    1. /**
    2. * 准备一个类,内部有一个属性 Obj2
    3. */
    4. public class Obj1 {
    5. // 需要注入 obj2
    6. private Obj2 obj2;
    7. // 省略其他方法
    8. ...
    9. }
    10. /**
    11. * 准备一个类,内部有一个属性 Obj1
    12. */
    13. public class Obj2 {
    14. // 需要注入 ob1
    15. private Obj1 obj1;
    16. // 省略其他方法
    17. ...
    18. }
    19. /**
    20. * 准备Obj2的代理类,内部持有obj2的对象
    21. */
    22. public class ProxyObj2 extends Obj2 {
    23. // obj2代理类内部持有obj2的原始对象
    24. private Obj2 obj2;
    25. public ProxyObj2(Obj2 obj2) {
    26. this.obj2 = obj2;
    27. }
    28. // 省略其他方法
    29. ...
    30. }
    31. /**
    32. * 准备Obj1的代理类,内部持有obj1的对象
    33. */
    34. public class ProxyObj1 extends Obj1 {
    35. // obj2代理类内部持有obj1的原始对象
    36. private Obj1 obj1;
    37. public ProxyObj1(Obj1 obj1) {
    38. this.obj1 = obj1;
    39. }
    40. // 省略其他方法
    41. ...
    42. }
    43. 复制代码
    • 首先准备了两个类:Obj1Obj2, 其中Obj1有个属性为Obj2Obj2中有个属性为Obj1
    • 接着准备了Obj1Obj2的代理类ProxyObj1ProxyObj2,并且ProxyObj1ProxyObj2分别有一个属性:Obj1Obj2
    • 我们依旧以new ObjX()模拟对象的创建;
    • 我们依旧以objX.setObjX(xxx)模拟依赖注入;
    • 我们依旧以new ProxyObjX(xxx)模拟代理对象的生成;
    • 我们依旧以collection.add(xxx)模拟对象添加到容器中的过程;

    我们模拟最终得到的结果为:

    • 最终放入容器的对象分别是proxyObj1proxyObj2
    • 注入到obj1中的是proxyObj2,注入到obj2中的是proxyObj2

    准备工作已经完成了,接下来我们就开始进行模拟了。

    3.1 模拟1

    要求:

    • Obj1与Obj2必须严格按照“创建-->属性注入-->生成代理对象-->保存到容器中”的流程创建
    • 两个对象的创建流程可以交替进行

    目标:

    • 最终放入容器的对象分别是proxyObj1proxyObj2
    • 注入到obj1中的是proxyObj2,注入到obj2中的是proxyObj2

    代码如下:

    1. public static main(String[] args) {
    2. // 准备一个容器,这里保存的是完成上述生命周期的对象
    3. // 1. 如果元素是原始对象,则该对象已经完成了属性注入
    4. // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
    5. Collection<?> collection = new ArrayList();
    6. // 1. 创建 Obj1 对象
    7. Obj1 obj1 = new Obj1();
    8. // 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
    9. // 一. 创建 Obj2 对象
    10. Obj2 obj2 = new Obj2();
    11. // 到这里,obj2需要注入obj1的代理对象,但此时容器中并没有obj2的代理对象,于是又要切到obj1的创建流程
    12. }
    13. 复制代码

    在执行以上流程中 ,发现创建 Obj2 对象后,流程就进行不下去了:

    • obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;
    • obj2需要注入obj1的代理对象,但找不到,于是切换到obj1的创建流程;
    • obj1需要注入obj2的代理对象,但找不到,于是切换到obj2的创建流程;
    • ...

    如此循环往复。

    模拟结果:未达到预期目标,本次模拟宣告失败。

    3.1 模拟2

    要求:

    • Obj1与Obj2必须以下两种流程之一创建:
      • “创建-->属性注入-->生成代理对象-->保存到容器中”的流程创建
      • “创建(原始对象)-->生成代理对象-->对原始对象进行属性注入-->将代理对象保存到容器中”的流程创建
    • 两个对象的创建流程可以交替进行

    目标:

    • 最终放入容器的对象分别是proxyObj1proxyObj2
    • 注入到obj1中的是proxyObj2,注入到obj2中的是proxyObj2

    示例代码如下:

    1. public static main(String[] args) {
    2. // 准备一个容器,这里保存的是完成上述生命周期的对象
    3. // 1. 如果元素是原始对象,则该对象已经完成了属性注入
    4. // 2. 如果元素是代理对象,则该对象持有的原有对象已经完成了属性注入
    5. Collection<?> collection = new ArrayList();
    6. // 1. 创建 Obj1 对象
    7. Obj1 obj1 = new Obj1();
    8. // 接下来需要将obj2的代理对象注入到obj1中,但此时容器中并没有obj2的代理对象,于是切换到obj2的创建流程
    9. // 一. 创建 Obj2 对象
    10. Obj2 obj2 = new Obj2();
    11. // 2. 对 Obj1 提前代理
    12. ProxyObj1 proxyObj1 = new ProxyObj1(obj1);
    13. // 二. 将 proxyObj1 注入到 obj2
    14. obj2.setObj1(proxyObj1);
    15. // 三. 生成 obj2的代理对象
    16. ProxyObj2 proxyObj2 = new ProxyObj2(obj2);
    17. // 四. proxyObj2 已经走完了完整的生命周期,将代理对象添加到容器时
    18. collection.add(proxyObj2);
    19. // 此时容器中已经有 obj2 的代理对象了,继续obj1的生命周期
    20. // 3. 将 proxyObj2 注入到 obj1
    21. obj1.setObj2(proxyObj2);
    22. // 4. proxyObj1 已经走完了完整的生命周期,将代理对象添加到容器时
    23. collection.add(proxyObj1);
    24. }
    25. 复制代码

    上面的代码中,obj1的流程用“1,2,3,4”标识,obj2的流程用“一,二,三,四”标识,两者流程如下:

    • obj1:“创建(原始对象)-->生成代理对象-->对原始对象进行属性注入-->将代理对象保存到容器中”
    • obj2:“创建-->属性注入-->生成代理对象-->保存到容器中”

    最终两者都存入了容器中,达到了预期的目标。

    3.3 从模拟中得到的结论

    对比上面两个模拟代码,发现模拟2之 所以能达到预期目标,主要是因为在注入obj2obj1属性时,提前生成了obj1的代理对象proxyObj1,使得obj2能完成整个创建流程。这里再次证明,提供进行aop对循环依赖的解决起到至关重要的作用!

    限于篇幅,本文就先到这里了,本文主要分析了循环依赖的产生,介绍了spring解决循环依赖的步骤,最后通过两段代码模拟了循环依赖的解决,下一篇文章我们将从spring源码分析spring是如何解决循环依赖的。


    本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    02-ROS的工程结构
    C语言--分段函数--switch语句
    人大加拿大女王金融硕士项目——追逐梦想,无论何时起步都不迟
    全域外卖推广怎么做才能赚钱?
    Java项目精选|Java毕业设计项目源码
    三篇学会MySQL数据库【查询详解】
    加入鲲鹏HPC训练营,一起引领高性能计算新潮流
    Vue记录(下篇)
    【C++】如何修改set的值
    1364. 顾客的可信联系人数量
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/127699914