带你彻底理解循环依赖

循环依赖

问题导入

A类,B类本来互相依赖,本来是没问题的:比如下面这种:
A a = new A();
B b = new B();

a.b = b;
b.a = a;

这样子,A,B就依赖上了。

但是如果这种依赖放在在Spring中就存在大问题,为什么?

是因为,在Spring中,一个对象并不是简单 new 出来就行,而是要要经过一系列 Bean 的生命周期,其中 Bean 的生命周期这会导致 循环依赖 问题,当然,出现问题的场景很多要分类,有的场景 Spring 可以帮我们解决,但是有的不能解决,后面仔细说。

如何解决?

解决办法:

三级缓存,其实就是三个Map,第一个就是单例池,后面有介绍:

那为什么要这样做?

先看Bean的生命周期

1 实例化

带你彻底理解循环依赖

我们目的:得到一个对象;如上面AService)=new Aervice(); 对象中的属性如 BService 是没有值哦;

2 填充属性=自动注入(bService属性)

怎么填充?

首先要有对象来填充才行,是吧? Spring 就到单例池里面去找 BService 对应的对象,因为 BService 跟AService一样也是 单例Bean 。但是有可能没找到,那我们就自己就创建 bService,但它也是个 Bean 啊,所以它要 从头开始bService 的生命周期的流程如下:
带你彻底理解循环依赖

但是有个问题啊?走到2.2的时候我们能不能找到aService这个Bean?

结论:不能。因为执行2.2时,你还在执行外边的第2步,aService 这时候还没有放到单例池,要最后才可以放进去,现在单例池里面没有,所以找不到。

那咋办?没办法,只能创建 aService,一夜回到*,开头 实例化AService 对象,然后填充bService属性,从单例池找bService 找不到,又创建 bService, 接着到2.2这步,还是没找到aService, 就这样造成一直循环依赖。如图:
带你彻底理解循环依赖

你说着不着急?

Spring如何解决?

只需加个Map就行。具体操作:

假如现在创建 tulingMap (随便写的),图中第一步把 AService 对象放入 tulingMap 里面,这时候到2.2步,不到单例池,到 tulingmap 里面找 AService 就能找到,因为你之前放的有呀, 并把它赋值给BService对象的 aService 属性。

这个循环依赖问题就可解决。

刚开始 AService 对象并没有给它赋值,当它被放入 Map 被找到,这个没有值的对象就赋值给 aService 属性。但其中是有 “问题” ! aService 这个对象有值才可以。

举个例子

/* 数字代表逻辑顺序 */

(1) A a = new A(); //相当于第一步实例化

(3) B b = new B();//相当于执行到2.1步
(4) b.a = a //给b对象的a属性赋值,就把a对象赋值给a属性, 这时候a对象里面的b属性也还是为空

(5) a.b = b;  //把b对象赋值给a对象的空的b属性是问题的,这个时候b属性就是有值,对应的a对象叫完整Bean对象 
(2) a.b = b;
结论:
那么我们就称没有值的属性对应的对象叫不完整的bean对象,反之;

疑问:没有bService属性,aService 对象怎么 new 得出来?

回答:

aService对象就是aService这个类的 无参构造 出来的,跟bService没有半毛钱关系的;

现在问题来了!

我们aService对象进行AOP的话,把切面打开,切的是AService的test()
带你彻底理解循环依赖
带你彻底理解循环依赖

如果让切面(AOP)生效的话,Spring会给 AService 生成一个代理对象的;

那AOP是在哪里做的?本文的第三步做:
带你彻底理解循环依赖

3 进行AOP(做其他事情)

例子中的 Aservice 对象而言,进行AOP时候,Spring会给 AService 生成一个代理对象的,前面已经说过,但是我们刚开始实例化的时候也是有个 AService 对象,如果到最后一步把对象放入单例池 ,应该把其中 哪个对象 放到单例池中呢?

答案:肯定是放AOP的代理对象嘛

2.2步给 aService 属性赋值的也是我们的代理对象,一定要这样用,这也是进行AOP的原因!

因为在项目的开发中间,我们是很少很少去 getBean 来给属性赋值的;

使用Spring框架,比如 controller 最终调BService的xxx方法,在这个方法里面使用 aService 对应的方法,是用到切面的,是这样用aService的,这就是为什么要给属性赋值的是代理对象,不然用aService的时候AOP是 没用的

现在目标:AService代理对象 赋值给bService生命周期中的aService 属性

正常情况下,aService要进行AOP的是在下面的第4步(做其他事情),但是这对循环依赖是个特殊的情况,怎么处理的呢?把AOP提前到第一步(提前进行AOP),在2.2步找的也就是代理对象。

重点来了!重点来了!要考哈

但是这是个特殊情况,要满足条件--当 AService 出现循环依赖的时候,那咱们就提前对他AOP。

那没有出现循环依赖时候呢,不用提前到第一步,把AOP放在图中第四步就行了。如图:
带你彻底理解循环依赖

提问:

那为什么不对每一个对象提前aop呢?提前AOP会有什么坏处?

回答:

简单的说,那对一个类或者一个对象AOP,是有条件的哦;

现在我们知道了是否已经发生了循环依赖,判断出 AOP 应该放在哪里;现在问题是 如何判断 AService 出现了循环依赖呢?

说实话,很不好判断。那就在整个过程中哪个地方 AService 出现循环依赖?

就在上图2.2步出现,这个时候给 aService 属性赋值时,我们去单例池找 aService 对象找不到,现在就在后面判断出 aService正在创建 中,我们就认为aService对象这时候就出现了循环依赖;

如何判断出AService正在创建中呢?

很好办,在实例化之前创建一个set(集合);

第0步 createingSet<aService> 

里面放的是AService的名字,表示正在创建的对象的名字,就知道是谁了,放入单例池后移除掉就可以。(creatingSet.remove('aservice') )

现在我知道 aService出现了循环依赖,这个时候我判断出要给它 提前AOP,生成它的代理对象,就可以给 aService属性 赋值喽,就这回事。
带你彻底理解循环依赖

4把单例对象或者叫Bean对象(AService)放到单例池(ConcurrentHashMap)它才可以单例

单例Bean,单例模式不同:

提问:AService肯定是单例Bean,那在整个Spring容器里AService这个类只有一个实例或一个对象?对还是不对

错的。
带你彻底理解循环依赖

上图中,也在其他类中创建两个不同的 AService 对象,只是名字不同,也没事,这和我们理解的单例模式不同哈。

解释单例池(singletonObjects)

就是一个map。而且是ConcurrentHashMap, 它的key就是Bean的名字,value是当前Bean对象。

解释下图:
带你彻底理解循环依赖

比如三次get到Bean,传的Bean的名字是一样的,并且每个是对应的是 单例Bean(如果是原型Bean那就不一样),那么三次getBean就是得到的是同一个对象aService;

说白了,单例Bean是跟我们的名字有关系。

那它是怎么达到这种效果?和ConcurrentHashMap的参数是一致的。

通过 AService 这个名字getBean就可以直接从Map里面找到 这个Bean对象。第二次的时候我还是通过 AService这个名字getBean在通过Bean又拿到了同样的对象。所以说,aService是单例Bean要将它放入单例池,为啥要放?

因为它是单例Bean,我必须把它放到单例池,以后直接在其中,拿到同一个对象。
上一篇:Wakawaka:华人企业用宜搭把数字化经验带进非洲


下一篇:linux安装python