什么是循环依赖
很简单就是一个对象依赖另外一个对象,比如:
class A{
// A依赖B
public B b;
}
class B{
//B依赖A
public A a;
}
A a = new A();
B b = new B();
a.b = b;
b.a = a;
这样A和B互相依赖,但是看起来也没什么问题,在spring中循环依赖就是个问题了?因为在spring中,bean并不是简单的new出来的,而是经历了一系列的生命周期,就是在生命周期里出现了循环依赖问题。当然有的循环依赖问题spring已经帮我们解决好了,有的需要我们自己解决。
bean的生命周期里循环依赖
得到一个完整的bean是需要填充属性
(依赖注入)和初始化后
(aop)的,循环依赖是发生在填充属性,那么具体过程是怎样的?
那刚才的例子来说,当A生成了一个原始对象之后,就会去给b填充属性
赋值,此时会根据b的类型和名字去BeanFactory中获取B
类的单例bean。如果BeanFactory中存在b,那么直接赋值给A的原始对象属性b,如果不存在则需要生成一个B对应的bean,然后赋值给b。
循环依赖就发生在第二种情况,也就是不存在B类对应的bean,需要去生成B类对应的bean,此时B类对应的bean需要经过完整的bean生命周期。那么在创建B类对应bean的时候,会发现需要A类对应的bean,此时A类对应的bean正在创建过程中,就导致了循环依赖。
ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中)
你也许会说直接拿正在创建的A不就行了?
不行,因为A还没有走完生命周期,并且在初始化后会经过动态代理,也就是现在有可能现在创建A和初始化后经过动态代理后的A不是同一个。也就是如果现在赋值给B类的a属性有可能不是最终A类生成的bean对象,如果真的想要解决循环依赖那么就需要提前走动态代理让B类提前拿到初始化后的经过动态代理的A类bean。在Spring中,通过某些机制帮开发者解决了部分循环依赖的问题,这个机制就是「三级缓存」。
解决循环依赖
三级缓存
在Spring框架中,三级缓存是指在创建Bean实例的过程中,Spring容器使用的三个缓存区域,它们分别是singletonObjects、earlySingletonObjects和singletonFactories。每个缓存区域都有不同的作用和功能,用于支持对象的创建、依赖注入和循环依赖解析。
-
一级缓存:singletonObjects,保存已经完全初始化的单例Bean对象。也就是经过完整生命周期的bean。 -
二级缓存:earlySingletonObjects,保存已经创建但尚未完全初始化的单例Bean对象。 -
三级缓存:singletonFactories,当需要解决循环依赖时,会将尚未初始化的Bean对象的工厂实例放入该缓存区域。这样可以在循环依赖的情况下,提供一个用于创建Bean对象的临时实例。
深入理解一下这三个缓存的关系
在属性注入的时候需要从缓存中拿值,具体源码如下
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先从一级缓存中取值
Object singletonObject = this.singletonObjects.get(beanName);
//如果一级缓存没有,并且该 beanName 对应的 bean 是正在创建过程中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//从二级缓存中拿
singletonObject = this.earlySingletonObjects.get(beanName);
//如果二级缓存没有,并且允许循环依赖(默认是允许的)
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 这里再次判断下,双重检查
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//直接看到这里,ObjectFactory是个接口
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//调用接口的方法,并且把返回的值放入到二级缓存中,并且将beanName从三级缓存中删除
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
一会再总结,先看什么时候会给三级缓存存值。
// 为了解决循环依赖提前缓存单例创建工厂
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 循环依赖-添加到三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// Initialize the bean instance.
Object exposedObject = bean;
try {
// 属性填充
populateBean(beanName, mbd, instanceWrapper);
// 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
通过源码可以发现,给三级缓存赋值的地方是再填充属性之前,也就是所有的bean在生命周期在实例化后,属性赋值前给三级缓存赋值,前提是spring允许循环依赖(默认是允许的)。
给三级缓存赋值的ObjectFactory接口实现:
/**
* Obtain a reference for early access to the specified bean,
* typically for the purpose of resolving a circular reference.
* 获取对指定bean的早期访问的引用,通常是为了解析循环引用。
*/
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
//提前进行AOP
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
好了,现在问题解决了。最大的问题就是放入缓存的没走完生命周期的bean和走完生命周期(也就是最后初始化后经过动态代理)的bean不是同一个。现在是当我们出现循环依赖了,我们先去让bean进行动态代理,拿着动态代理之后的bean去进行依赖注入,并且还可以继续A原始Bean的生命周期,因为代理后的bean内部有一个变量target,target指向A原始的bean,所以A原始bean继续走之后的生命周期不会影响到代理后的bean。
其实这里边还有一个缓存就是earlyProxyReferences
,在我们提前进行AOP的时候,会给这个缓存赋值。也就是说A在生命周期的最后进行AOP的时候会判断这个缓存里边有没有,如果有就跳过了。
总结一下:
还是用A,B类去举例说明,我们把没经过完生命周期的bean叫做原始bean,把经过AOP的bean叫做代理bean。
「先总结三级缓存中取值」:
-
首先从一级缓存中取值,如果存在直接返回。 -
如果一级缓存中没有,并且这个bean是正在创建的,就从二级缓存中拿,如果二级缓存中有直接返回。 -
如果二级缓存中没有,并且spring支持循环依赖。 -
双重检查锁,重新走1,2步骤 -
从三级缓存中拿,三级缓存中存放的是ObjectFactory就是一个接口。 -
执行ObjectFactory也就是让这个bean先执行AOP,然后把返回值,也就是代理bean存放到二级缓存中,并且把三级缓存中的值删除。
「spring解决循环依赖」:
-
创建A原始bean -
经过生命周期,实例化后,属性赋值前,会给三级缓存添加一个ObjectFactory的方法(方法内容是提前进行代理) -
进行属性注入时候发现B,B没有在缓存中。 -
A中止创建,开始创建B的原始bean。 -
B经过生命周期,实例化后,属性赋值前,会给三级缓存添加一个ObjectFactory的方法。 -
属性注入,发现需要注入A。 -
从三级缓存中拿值,并且赋值给B的属性,然后B走完生命周期。 -
将走完生命周期的B存放到singletonObjects中。 -
A原始bean继续走生命周期。 -
当A原始bean生命周期到初始化后,也就是AOP那步骤,会判断缓存 earlyProxyReferences
有没有提前AOP,如果有,就无需再进行AOP。 -
要从二级缓存 earlySingletonObjects
中得到代理对象A,然后入singletonObjects中。