循环依赖
问题导入
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,我必须把它放到单例池,以后直接在其中 拿,拿到同一个对象。