循环依赖是什么?
A依赖B依赖A形成环。
解决循环依赖需要:
Bean必须是单例的
不能以构造器注入的方式注入(为什么?构造器注入就意味着是强依赖,意味着注入时参数对象已经准备好了(不会报空指针异常),但循环依赖却无法完全准备好创建对象这样会直接报错(NoSuchBeanDefinitionException))
但构造器注入可以通过:
@Lazy 注解来解决,因为其返回给构造器参数一个代理对象,当调用这个依赖代理对象时,会通过cglib(因为该代理对象不是接口)的Intercept方法内部有一个targetsource.gettarget来获取被代理的对象target,而targetsource.gettarget方法中有一个beanfactory.doresolverDependency()方法作用就是通过beanfactory解析真正代理对象的真正的对象,将IoC 中真实的对象返回获得真正的对象,通过反射完成调用
@Lazy
用于指定单例bean实例化的时机,在没有指定此注解时,单例会在容器初始化时就被创建。
而当使用此注解后,单例对象的创建时机会在该bean在被第一次使用时创建,并且只创建一次。第二次及以后获取使用就不再创建。
其他的循环依赖事实上是通过三级缓存加提前暴露解决的
三级缓存:
一级缓存 singletonObjects(ConcurrentHashMap) 用于存放完全可以使用单例bean,也就是初始化完成并且注入所有依赖
二级缓存 earlySingletonObjects (HashMap) 过早暴露单例对象,此时bean刚刚完成初始化,未完成属性注入和执行 init 方法(一般处于循环引用状态的Bean才会被保存在该状态)
三级缓存 singletonFactories (HashMap) 装载创建bean的工厂对象
对象创建的缓存流转流程:
创建对象,并将他的ObjectFactory首先放入三级缓存中(如果IoC允许Bean之间循环引用),
如果没有循环依赖,则会在生命周期的相关方法执行完毕,将这个对象保存到一级缓存中,同时删除二级缓存以及三级缓存中对象。
对象的实例化过程:
先从一级缓存获取对象,
没有?判断这个对象是不是正在创建: _isSingletonCurrentlyInCreation_():return this.singletonsCurrentlyInCreation.contains(beanName);
(歪个题:这里的singletonsCurrentlyInCreation 是一个
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
)
还没有?判断二级缓存有没有
还是没有,再判断一下是不是允许earlyreference(提前暴露)
允许且没有对象:那我不信
我再从一级缓存开始查一直到三级缓如果都没有那确实是没有,但如果三级缓存有了,那就会把它放在二级缓存中,并移除三级缓存中这个对象;
如果确实都没有,就开始创建对象getObjectFromFactoryBean
来创建对象
创建完之后就会走populate
属性注入和exposedObject
暴露Bean(让其他人可以aware这个bean)
创建完之后就将放在一级缓存中(只有单例才会缓存)
public class A{
@Autowired
B b;
}
public class B{
@Autowired
A a;
}
循环引用发生时,
比如此时正在创建A对象,实例化好A对象之后,将ObjectFactory(之所以是工厂而不是bean,就是想再调用的时候才去生成对象)放入到Spring的三级缓存,
此时实例化完A了,就要开始走populateBean
注入属性了,发现这个属性B是个Bean,就会去实例化这个Bean,
实例化完B后,走属性注入,发现依赖的A是个Bean,又会去实例化这个A;
然后就去实例化这个A,然后实例化过程中,会去检查缓存中有没有A,恰巧三级缓存中有A,此时会走Singletonfactory.getObject方法,(其实底层调用了getEarlyBeanReference
=A的早期引用,也就是这个半成品A直接返回出去了)得到了这个A并把它放在二级缓存中,并移除三级缓存中的对象
此时B拿到了A的早期引用,可以完成B的完整实例创建;
然后返回来A继续往下走,发现可以从一级缓存中取到B,A也完成创建
当B已经完成初始化方法和属性注入,再将缓存添加到一级缓存中,并且删除二级缓存。
以上过程可以看出第三级缓存好像没什么用,在创建B的时候,通过ObjectFactory得到bean得到的依然还是原来的A,相当于啥事也没干,那我是不是就不需要三级缓存了?
其实,如果是有一方依赖对方的代理对象(AOP增强过的,一般依赖注入的就是代理对象)的情况下,三级缓存就有必要了,
也就是说如果有AOP介入就有必要了
AOP介入就意味着会产生代理对象;
AOP创建代理的时机一般是在
属性注入完了,开始暴露对象,这里会走applyBeanPostProcessorsAfterInitialization
方法,这里面有一个getBeanPostProcessors
方法,其中有有一个AnnotationAwareAspectJAutoProxyCreator
就是专门用于生成AOP代理对象的处理器,其中会走warpIfNecessary
判断是否创建代理对象,–判断是否有增强:getAdvicesAndAdvisorsForBean
有的话,就会调用createProxy
方法创建代理对象proxy–>先创建AOPproxy(这里根据目标类的类型决定时jdk动态代理的aopproxy还是cglib动态代理的aopproxy)确定之后再调用getProxy得到代理对象
得到代理对象之后,就将其存到一级缓存中
但是循环依赖了,则需要提前创建代理对象:
过程就是初始化对象A(原始对象),并将它的ObjectFactory放入三级缓存中 (并没有生成A的原始对象)
注入B时,发现三级缓存都没有B对象,则创建对象,
并将B初始化、放入三级缓存中,
B继续创建发现B 又 依赖 A ,因此在三级缓存中找到A对象,调用A的Objectfactory方法得到代理对象A【这是个半成品】,将这个半成品放在二级缓存,并将三级缓存的A移除,【此时生成的为A的代理对象,保证Spring中A对象自始至终只有1个且对象的地址没有变化,因为代理前和代理后的对象不是一个对象,内存地址都不同!】
取到A对象,B就完成创建,并放B到一级缓存中
A获得B 得到的完整品A放到一级缓存中,将二级缓存中的A删除
A(原始对象)完成创建,再去创建B,可以从直接拿到B对象,无需创建,循环依赖解决
也就是说B的创建是基于代理对象创建的,
而调用B中的A其实是个代理对象,调用这个代理对象的方法时,代理对象的代理类都会持有目标类的引用,这就意味着调用代理对象的方法实际就是调用目标对象的方法,那么原始对象完成了初始化,代理对象也就完成了初始化
区别就在于用工厂getObject时,返回的对象,如果没被AOP过返回的就是原始对象,如果需要被AOP,则此时就会提前创建代理对象,因此第三级缓存就发挥大作用了
参考:
https://zhuanlan.zhihu.com/p/157611040
============================================================================
补充:
属性注入的方式有:
使用Setter方法
class B{
A a;
@Autowired
public void setA(A a)
{
this.a = a;}
}
使用构造器注入
class B{
A a;
@Autowired
public B(A a)
{
this.a = a;}
}
是Spring团队推荐的注入方式(但其实会存在构造函数注入参数过多导致代码冗余的问题),因为其能保证被调用时构造器参数的对象已经完全准备好,属于强依赖,因此不会出现空指针,能保证不变性
基于注解
本质就是通过反射直接注入field,spring团队不推荐,不推荐使用该方式构建不可变对象
class B{
@Autowired
A a;
}
具体注入原理还有待补充…