spring循环依赖简而言之就是多个bean之间存在相互依赖关系,跟Java语言中对象间的相互依赖本质是一样,可以把spring的bean看成Java中的对象,两个对象相互依赖,在Java中是可以的,但是在某些情况是不可行,下面举例看看哪些可以,哪些不行:
public class A {
private B b;
}
public class B {
private A a;
}
上面两个类A,B存在相互依赖,那么在创建对象的时候不管先创建谁,都可以给这两个字段提供setter方法,等两个对象创建完毕,再用setter方法进行赋值。这样就可以让两个对象相互依赖,如下:
public static void main(String[] args) {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
}
那么要是两个类没有setter方法,而是通过构造方法给字段赋值的呢?
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
这样的话就没办法将两个对象先创建出来再进行赋值,因为在创建对象的时候就需要依赖对方,没有对方的对象直接就编译报错,如下:
public static void main(String[] args) {
A a = new A(); //编译报错,需要B对象,尝试下面方法呢?
A a1 = new A(new B()); //同样编译报错,B对象创建又需要A对象
}
所以通过上面简单的例子,就可以知道,通过构造函数注入属性的循环依赖是没有办法解决的,因为Java语言中编译就报错了,spring也如此,无法处理构造方法的循环依赖注入(如果两个类还有有默认的构造方法,那么通过反射其实也可以完成,但是这也就违背了原则,而且spring也没法保证这个类就一定有默认构造)。
Spring循环依赖的三种方式
通过上面的demo就可以知道spring的循环依赖已有两种,setter依赖,和构造方法依赖,其实setter方法依赖需要再细分,就是单例setter循环依赖和多例的循环依赖。
单例的setter循环依赖是可以解决的,因为创建的对象会一直存在,两个对象也可以一直相互引用。而多例的setter循环依赖无法解决,因为多例对象用的时候才创建,用完就销毁,这样双方并不知道对方存在的具体时间,有可能A在,B不在了,那么通过A对象获取字段B,而B已经不在了,那么就会有问题。
通过原生简单的Java代码分析了循环依赖,spring也是按照这种大体的设计逻辑,进行编码的。结论就是:
- 单例setter循环依赖可以解决
- 多例setter循环依赖无法解决
- 构造函数循环依赖无法解决
- @DependsOn无法解决
- 单例的代理对象有可能解决
spring是如何解决循环的
所谓的spring是如何解决循环依赖,就是解决单例对象间通过setter方法注入的循环依赖,这种依赖在原生的Java编码是可行,spring也需要保证这一行为。
先给出结论,再结合源码分析。spring的具体做法就是:
- 将创建的对象在不同时机放入到不同的缓存,一共三个,称三级缓存。
- 一级缓存-singletonObjects 存放创建完成的单例bean
- 二级缓存:earlySingletonObjects 见名知意,早产的单例对象,即存放不完整还没创建完成的单例
- 三级缓存:singletonFactories 这个缓存主要是用于解决有可能生成代理的单例对象,没有代理则返回原生对象,即AOP
步骤:
- 创建对象A时先创建一个空对象,将bean的相关信息包装成一个ObjectFactory放入‘早期暴露’工厂缓存(三级缓存),有aop时会调用这个工厂方返回相应的代理对象
- 然后再注入属性B,那么到容器中查找先找一级,二级,三级缓存,此时都没有找不到,就创建B,同样的放入三级缓存。
- 创建B时发现需要A,那么就找A,按照缓存顺序查找,一二级没有,在三级中找到,那么此时需要拿到A,这里需要注意的时,如果A有aop则返回的是代理对象,如果没有则返回A对象本身,在返回A(可能是代理也可能是对象本身)的同时,把A放入到二级缓存,删除三级缓存的中的A工厂
- B对象拿到A时,属性注入完成,后续其他操作完成时,删除二三级缓存中的B,放入一级缓存,那么B创建完成,此时A就可以注入B了
- 然后A完成后续操作,再次从二级缓存获取(先走一级,找不到)到A把引用给A的变量,此时才是完整的A对象(B创建完成也存在这一步)
- 如有其他的依赖循环执行
源码解析
有了上面的大概逻辑之后,再来结合源码分析下整个流程,以及为什么需要用到三级缓存。
首先spring的逻辑是不管是什么bean,先获取,获取不到就创建。假如是A对象(A1),调用getBean再调用doGetBean(),找三个缓存
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
这时找不到,就开始创建对象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...
beforeSingletonCreation(beanName); //将bean加入到正常创建的集合
...
}
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
...
instanceWrapper = createBeanInstance(beanName, mbd, args); //创建对象实例
...
synchronized (mbd.postProcessingLock) {
if (!mbd.postProcessed) {
try {
/*
* 这里会执行spring的三个后置处理器:
* AutowiredAnnotationBeanPostProcessor 获取当前bean是否有使用@Autowride,@Value注解标注的字段,并记录,方便后面注入
* CommonAnnotationBeanPostProcessor
* ApplicationListenerDetector 什么也没干
* */
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
}
catch (Throwable ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Post-processing of merged bean definition failed", ex);
}
mbd.postProcessed = true; //表示已经执行过了
}
}
...
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {//如果能进入到这个if那么就说明,当前的spring容器是支持单例bean的循环依赖,而且这个bean已经在创建,那么加入到三级缓存
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
/*
* 放入常说的’三级缓存‘
*
* */
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
//如果有aop时,这里会有一个aop的smartInstantiationAware后置处理器的实现类,来创建一个代理对象
//没有则直接返回原对象
return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
}
});
}
}
这里是在创建实例A1完成之后就放入了三级缓存,放入的是个lamda表达式,为什么要放lamda表达式?继续往下看
populateBean(beanName, mbd, instanceWrapper);
if (hasInstAwareBpps) {
if (pvs == null) {//这里再次调用getPropertyValues()是为了给个默认值
pvs = mbd.getPropertyValues();
}
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
在这里会执行属性注入,那么获取spring的容器中查找该bean,如果需要B,那么又会调用上面的getSingleton方法,获取B,同样的放入正在创建的集合,创建完成放入三级缓存,继续调用populateBean注入A,需要A时再次从三个缓存中查找,这时三级缓存中是有A的,那么此时就会调用存入的lamda表达式获取A对象(这里获取到的称为A2),即调用lamda表达式的getEarlyBeanReference方法,如下:
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
}
});
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
//这里如果有AOp那么 会有一个AbstractAutoProxyCreator的实例会执行,并创建一个代理对象
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
在调用getEarlyBeanReference方法时,会执行smartInstantiationAware这个集合,并增强bean,这里通常返回的是原来的bean,而有AOP代理时,这里返回的就是增强的bean。这就是为什么三级缓存存入的是一个lamda表达式,通过lamda的回调方法很巧妙的处理了不同的需求。
在获取到A时,就将三级缓存中的A2移动到二级缓存。为什么要移动到二级缓存?继续看
那么此时找到了A2,接着继续将A2注入给B的属性,如果有AOP的话,那此时原来正在创建的A1和注入给B的A2并不是同一个对象,B注入完成之后,会调用initializeBean方法中的bean的后置处理器。
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
//这里如果有AOP会执行相关的方法
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
AbstractAutoProxyCreator也实现了这个接口,这是AOP的回调,此时如果有aop那么就会创建代理对象,所以当b执行完毕之后,有aop那么B2也是一个代理对象,不在是原来的B1对象,返回B,放入一级缓存。
此时B2已经创建完成了,不管他是代理对象还是原生对象都会返回给原来的B1的变量,注入给原来的A1。现在B中的A2不管是不是原来的A1都不重要,重要的是spring是如何保证原来的A1 和B中的A2是同一个对象的。
在A2注入完成B之后,同样会执行上面的bean的后置处理器方法,只不过再次执行相关AOP回调时,已经创建过了,就不会再创建。在最后还会调用
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);//缓存中的bean可能时代理对象
if (earlySingletonReference != null) {
if (exposedObject == bean) { //初始化之后的bean和实例化的bean是否为同一个
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
在这里会再次从缓存中获取A2,他还在二级缓存中,也是B中引用的A2,将这个A2赋值给原来的A1,保证了统一。
那再看看这里如果没有移动到二级缓存,而是三级缓存,那么此时如果从三级缓存获取,则会创建再次创建一个代理对象,就是A3,那么后面就没法控制A的一致性
为什么需要三级缓存解决循环依赖
按照我们习惯的开发思维,越简单实现就越好,不用缓存行不行,那用一级缓存行不行,不行的话用两个缓存行不行?
- 首先不用缓存肯定不行,因为需要有地方存放已经创建好的单例bean。
那用一个缓存行不行?
如果只是单单的从解决bean的相互依赖的问题出发,不考虑其他,其实也是可以解决部分的依赖的。假如都是setter注入的bean,A创建好就放入缓存,注入B,发现B没有,创建B放入缓存,再注入A,从缓存里找可以找到,注入给B,B完成创建,A注入B,这样就可以解决了,但是spring考虑的不单单是依赖注入,还有其他的扩展,如AOP。那么将AOP提前到放入缓存之前呢?这也可以的,但是问题是只有一个缓存的话,那么用户获取的时候就可能获取到的不是一个完整的bean,可能是正在创建的bean用两个缓存行不行?
跟上面一样,如果只考虑循环依赖的结果,或者某种结果,也是可以,只不过spring是一个庞大的体系,循环依赖只是bean生命周期的冰上一角,spring要考虑不单单是循环依赖,还有其他的很多东西。从设计和使用,效率的角度来看,在庞大的spring体系中,可能使用三级缓存是他目前最好的实现方式吧