8-Spring循环依赖的处理逻辑
1. 目录
[TOC]
2. 说明
spring 解决循环依赖主要用到三层缓存,即singletonObjects
、earlySingletonObjects
、singletonFactories
三个缓存,此三个缓存是三个Map
作为DefaultSingletonBeanRegistry
的成员属性
singletonObjects
类型是ConcurrentHashMap
earlySingletonObjects
类型是ConcurrentHashMap
singletonFactories
类型是HashMap
3. 依赖分析
DefaultSingletonBeanRegistry
是单例bean的容器类,bean存储相关的都在此,我们应用中的BeanFactory
和ApplicationContext
都继承于此。
其方法主要实现了单例bean的获取,其中包含了创建bean的匿名实现,即传入ObjectFactory
,内部getSingleton
方法的逻辑中会在没有拿到数据的时候调用getObject()
方法,从而调起外部传入的创建bean的方法,再保存在单例容器中。上图中是getSingleTon(String,ObjectFactory<?>)
和addSingleTon(String,ObjectFactory<?>)
两个方法。
创建bean的逻辑则主要是在子类AbstractBeanFactory
中,内部包含了createBean()
和doCreateBean()
等方法,其匿名内部类的实现也是doBean()
方法。AbstractBeanFactory
本身也会继承DefaultSingletonBeanRegistry
,因此,AbstractBeanFactory
也具有维护容器的能力
4. 源码分析
源码部分我们就主要拿重点的部分讲解了,其实也就是整合getBean和createBean两个逻辑提取,主要的类为AbstractBeanFactory
首先,实例化单例bean循环调用getBean()
方法,而getBean
方法直接调用doGetBean
,首先会从容器中获取,如下:
4.1. 获取bean
1 | protected <T> T doGetBean( |
4.2. 创建bean
1 |
|
4.3. 单例容器的方法
1 | // 1. 获取单例bean |
1 | //3.添加一级缓存 |
5. 流程分析
5.1. 普通Bean下的循环依赖
通过上面的分析, 我们通过实例进一步了解。
对于A(B,C),B(A,C)即A类中有属性BC,B类中有属性AC
假如首先getBean()初始化A,且允许循环依赖
- (A)调用
getSingleton(beanName,true)
获取A的单例,此时三层缓存中都没有,因此获取不到。
A-无缓存,B-无缓存 - (A)调用
getSingleton(beanName, () -> {return createBean(beanName, mbd, args);}
方法,此时从一级缓存中也拿不到,然后就会调用内部方法createBean()
。完成之后再加入一级缓存,从三级缓存移除(此处待机,等待回调)。
A-无缓存,B-无缓存 - (A)调用
createBean()
创建bean,首先实例化bean,然后调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
加入到三级缓存中,并从二级缓存移除。
注意必须开启循环依赖才会进入addSingletonFactory()
方法,因此无循环依赖的不会加入到三级缓存中,步骤6则拿不到缓存,进而循环,报循环错误
注意此时已经实例化的bean在getEarlyBeanReference()
中
A-三级缓存,B-无缓存 - (A)
populateBean()
此时填充bean,开启对象B的getBean()
A-三级缓存,B-无缓存 - (B) B还未生成bean,同样执行前面4个逻辑,到了
populateBean()
,准备注入A
(1)A-三级缓存,B-无缓存
(2)A-三级缓存,B-无缓存
(3)A-三级缓存,B-三级缓存
(4)A-三级缓存,B-三级缓存 - (B) 再调用A的
getBean() -> getSingleton(beanName,true)
时,由于A已经在三级缓存中了,因此这里能够取到值,然后将A添加到二级缓存,从三级缓存中移除,并且返回A
A-二级缓存,B-三级缓存
注意此处的三级缓存实际是第三步的匿名内部类,但是对于普通的bean,返回的仍然是原来的bean,此处是处理代理类的 - (B) B注入A则可以成功,继续装配B,执行初始化流程
initializeBean()
等;
注意 在初始化initializeBean()
过程中B会调用AnnotationAwareAspectJAutoProxyCreator
这个BeanPostProcessor
执行代理方法wrapIfNecessary()
,得到代理后的Bean
A-二级缓存,B-三级缓存 - (B) B通过
getSingleton(beanName, false)
拿一次一二级缓存中的bean,由于B此时在三级缓存中,因此获取不到,还是按照步骤7中的返回,但此时B已经注入了A、完成组装,而且已经是代理后的Bean。
A-二级缓存,B-三级缓存 - (B) B回到步骤2,加入B到一级缓存,并从二三级缓存中移除。
A-二级缓存,B-一级缓存 - (A) 回到A注入B处,A注入B成功,继续执行初始化流程等
注意 此处执行初始化时A也会像B的步骤7一样进入AnnotationAwareAspectJAutoProxyCreator
,但是缓存中有是否已经代理过了,代理过了则不代理了,因此不会再代理一次,此处返回不代理的结果。
A-二级缓存,B-一级缓存 - (A) A通过
getSingleton(beanName, false)
拿一次一二级缓存中的bean,由于A在二级缓存中了,此处可以得到A,返回A,然后返回此处的Bean。
A-二级缓存,B-一级缓存 - (A) 回到步骤2,加入A到一级缓存,并从二三级缓存中移除。
A-一级缓存,B-一级缓存
这样就解决了循环依赖,其实我们可以发现步骤3getEarlyBeanReference()
这里匿名内部类加入三级缓存,然后在步骤6第二次对AgetBean
时加入二级缓存,最后再回到步骤2加入一级缓存,这里看只需要二级缓存,不要三级缓存也是一样的,而且步骤3也没必要在弄一个匿名内部类。
但是,我们这里没有说到动态代理的情况,spring下很多地方都会用到动态代理。动态代理是在getEarlyBeanReference()
有特殊处理的,见下
5.2. 动态代理下的循环依赖
对于A(B)
,B(A)
且C代理A,D代理B,则包含关系有C(A(B))
,D(B(A))
对于上述关系,实际上B因为被D代理了的,因此A中的B应该为 D(B)
才合理,因此正确的应该为C(A(D(B)))
,同理得到D(B(C(A)))
假设用E代替C(A)
,F代替D(B)
,则他们的循环关系应该为 E(F(E(F...)))
,即E->F->E
简单的来说就是他们互相的属性字段应该为代理后的对象。
对于上述的普通循环代理,我们可以得知:
- 步骤3
getEarlyBeanReference()
是在三级缓存中的匿名内部类, - 步骤6 从三级缓存中去拿的时候调用内部方法,通过
AbstractAutoProxyCreator#getEarlyBeanReference
调用wrapIfNecessary()
生成代理对象(这个在AOP动态代理分析处解析,专门生成动态代理的)。此时,我们加入二级缓存中的A
对象已经是代理之后的对象E
了。 - 步骤7 B中装配的实际上就是代理之后的对象
E
- 步骤7 的执行初始化流程
initializeBean()
会循环调用applyBeanPostProcessorsAfterInitialization()
方法,此方法会调用AbstractAutoProxyCreator#postProcessAfterInitialization()
,进而调用wrapIfNecessary()
,此时生成B的代理对象F
- 步骤8 由于上述改变了B的返回对象为F,因此步骤8返回的为
F
- 步骤9 加入到一级缓存中的则为F,同时删除三级缓存,由于B并没有执行三级缓存的读取,因此 步骤5(3) 加入的
getEarlyBeanReference()
并没有生效,保证了只执行了一次创建代理wrapIfNecessary()
流程 - 步骤11 A在执行此步骤之前也会像B一样
- 其余流程均为一致
此处可以发现,A和B创建代理wrapIfNecessary()
的入口方法不同,A是在三级缓存中匿名内部类getEarlyBeanReference()
调起创建,而B则是在初始化方法完成时的后处理中postProcessAfterInitialization()
,
其原因是A是在填充属性populate()
中调用的B;B是先调用A,在B的填充A时候调用getBean()获取A的时候就代理了A,也保证了B中的A是代理之后的;B填充A完成之后初始化自身时的后处理器代理了B。再到A的初始化自身的后处理时A已经被代理过了,不用再重新生成。
此处还有一个知识点:B拿到A的代理对象F时,实际上F里有A的实例bean,虽然F不会被修改了,但是后续B初始化完成后初始化A时,F中的A保持着引用,而动态代理执行方法时最终也会调用到源类型,因此才保证了相互持有的代理
以下是画了一个带代码的流程图,可以看一下
6. 总结
首先分析无动态代理的循环依赖,主要是在容器之间的一个对缓存的处理,利用了java引用的特性,保证地址空间互相引用到,然后就可以初始化自身了。
同样有动态代理下的也利用了java的引用特性,才保持了代理类可以相互注入并保持循环依赖
同时需要注意两个代理类的实际也都不一样,虽然都是调用了wrapIfNecessary()
实现动态代理,但是A是在B调用A的时候A的getBean()
中调用三级缓存匿名内部类调起的,而B是在B自身创建initialize()
中通过postProcessAfterInitialization()
完成。
7. 注意事项
网上大部分都只是说getEarlyBeanReference()
是解决循环依赖的,实际上这并不准确。因为单纯的解决循环依赖也不需要三级缓存,两级也可以实现的,而且也不需要getEarlyBeanReference()
匿名内部类。
准确的来说应该是getEarlyBeanReference()
是为了解决动态代理下的循环依赖的。Spring的思想就是在生命周期的最后执行AOP代理,而不是在创建的时候就执行代理,对于循环依赖必须要提前执行代理,因此才需要二级缓存提前完成代理,对于不需要循环依赖的,则二级缓存无意义,压根就没有使用;