揭秘Spring内部如何解决循环依赖
本文作者:FUNKYE(陈健斌),杭州某互联网公司主程。
起因
先上引起循环依赖的demo
1 |
|
通过分析可以发现,A依赖于B,B依赖C,C又依赖A.这就是个循环依赖的例子
分析源码
1.首先我们要了解到spring的三级缓存机制.
1 | singletonObjects指单例对象的cache (一级缓存)存放初始化好的bean |
2.通过三级缓存我们可以发现spring单例对象的创建是分为3步骤.
1 | protected Object getSingleton(String beanName, boolean allowEarlyReference) { |
3.分析源码得知,我们可以发现,首先是从一级缓存中去取这个bean的实例,如果没取到的话,通过isSingletonCurrentlyInCreation方法获取bean是否在创建的过程中.
4.如果要获取的bean还在创建中,那么会继续往下走,从二级缓存去获取,如果此时二级缓存中还未存放bean时,那么判断allowEarlyReference是否等于true,是的话就允许我们去三级缓存中获取.
5.从三级缓存中取到刚实例化好的bean时,把bean放入二级缓存,从三级缓存中剔除掉.
A完成初始化时,发现自己依赖于B,那么这时候会尝试获取B的示例,但是发现B还没被创建这时候,B也开始初始化,又发现自己依赖于C,于是C也开始了初始化,这时候,C发现了A在一级缓存中没有,继续向下寻找(其实可以知道二级缓存也不会存在A),这时候C在三级缓存中找到了A,这时候再把A放到二级缓存中,至此,ABC都可以顺利实例化了.
那么问题来了,A是怎么进入到三级缓存中的呢?
1 | protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { |
这段代码就是解决循环依赖的关键,这段代码发生在createBeanInstance之后(单例对象的初始化三步骤的第一步),也就是上面那段代码会把一个刚做完初始化第一步的A放入到三级缓存中,虽然这个实例还不完美,但是大家都知道他是A就行了,spring就是利用这样的提前曝光来解决循环依赖的问题,之后待初始化完成后ABC就会将自己放入1级缓存中.
总结
Spring通过三级缓存加上“提前曝光”机制,配合Java的对象引用原理,比较完美地解决了某些情况下的循环依赖问题,但是通过这些原理也能知道,如果A的构造方法里依赖了B,B的构造方法依赖于C,C也依赖A时,其实上述方法并不能解构造器的循环依赖问题,因为A如果想提前曝光,必须要执行完构造器,这样才能加入到三级缓存中,这样也不难解释,为什么还会出现一些循环依赖问题.
解决方案:
1.其中一方加入@lazy
2.使用 Setter/Field 注入
3.使用 @PostConstruct
4.实现ApplicationContextAware and InitializingBean接口
之后可能会在后面的博文中上传.