Spring 源码剖析之循环依赖及解决方案
注释:
首先,咱们须要明确什么是循环依赖?简略来说就是 A 对象创立过程中须要依赖 B 对象,而 B 对象创立过程中同样也须要 A 对象,所以 A 创立时须要先去把 B 创立进去,但 B 创立时又要先把 A 创立进去 … 死循环有木有 …
那么在 Spring 中,有多少种循环依赖的状况呢?大部分人只晓得两个一般的 Bean 之间的循环依赖,而 Spring 中其实存在三种对象(一般 Bean,工厂 Bean,代理对象),他们之间都会存在循环依赖,这里我给列举进去,大抵别离以下几种:
- 一般 Bean 与一般 Bean 之间
- 一般 Bean 与代理对象之间
- 代理对象与代理对象之间
- 一般 Bean 与工厂 Bean 之间
- 工厂 Bean 与工厂 Bean 之间
- 工厂 Bean 与代理对象之间
那么,在 Spring 中是如何解决这个问题的呢?
1. 一般 Bean 与一般 Bean
首先,咱们先构想一下,如果让咱们本人来编码,咱们会如何解决这个问题?
栗子
当初咱们有两个相互依赖的对象 A 和 B
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {this.normalBeanB = normalBeanB;}
}
public class NormalBeanB {
private NormalBeanA normalBeanA;
public void setNormalBeanA(NormalBeanA normalBeanA) {this.normalBeanA = normalBeanA;}
}
而后咱们想要让他们彼此都含有对象
public class Main {public static void main(String[] args) {
// 先创立 A 对象
NormalBeanA normalBeanA = new NormalBeanA();
// 创立 B 对象
NormalBeanB normalBeanB = new NormalBeanB();
// 将 A 对象的援用赋给 B
normalBeanB.setNormalBeanA(normalBeanA);
// 再将 B 赋给 A
normalBeanA.setNormalBeanB(normalBeanB);
}
}
发现了吗?咱们并没有先创立一个残缺的 A 对象,而是先创立了一个空壳对象(Spring 中称为晚期对象),将这个晚期对象 A 先赋给了 B,使得失去了一个残缺的 B 对象,再将这个残缺的 B 对象赋给 A,从而解决了这个循环依赖问题,so easy!
那么 Spring 中是不是也这样做的呢?咱们就来看看吧~
Spring 中的解决方案
因为上一篇曾经剖析过 Bean 的创立过程了,其中的某些局部就不再细讲了
先来到创立 Bean 的办法
AbstractAutowireCapableBeanFactory#doCreateBean
假如此时在创立 A
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
// beanName -> A
// 实例化 A
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 是否容许裸露晚期对象
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 将获取晚期对象的回调办法放到三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
}
addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {
// 单例缓存池中没有该 Bean
if (!this.singletonObjects.containsKey(beanName)) {
// 将回调函数放入三级缓存
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
ObjectFactory 是一个函数式接口
在这里,咱们发现在创立 Bean 时,Spring 不管三七二十一,间接将一个获取晚期对象的回调办法放进了一个三级缓存中,咱们再来看一下回调办法的逻辑
getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 调用 BeanPostProcessor 对晚期对象进行解决,在 Spring 的内置处理器中,并无相干的解决逻辑
// 如果开启了 AOP,将引入一个 AnnotationAwareAspectJAutoProxyCreator, 此时将可能对 Bean 进行动静代理
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
在这里,如果没有开启 AOP,或者该对象不须要动静代理,会间接返回原对象
此时,曾经将 A 的晚期对象缓存起来了,接下来在填充属性时会产生什么呢?
置信大家也应该想到了,A 对象填充属性时必然发现依赖了 B 对象,此时就将转头创立 B,在创立 B 时同样会经验以上步骤,此时就该 B 对象填充属性了,这时,又将要转头创立 A,那么,当初会有什么不一样的中央呢?咱们看看 getBean 的逻辑吧
doGetBean
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){
// 此时 beanName 为 A
String beanName = transformedBeanName(name);
// 尝试从三级缓存中获取 bean, 这里很要害
Object sharedInstance = getSingleton(beanName);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从单例缓存池中获取,此时依然是取不到的
Object singletonObject = this.singletonObjects.get(beanName);
// 获取不到,判断 bean 是否正在创立,没错,此时 A 的确正在创立
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 因为当初依然是在同一个线程,基于同步锁的可重入性,此时不会阻塞
synchronized (this.singletonObjects) {
// 从晚期对象缓存池中获取,这里是没有的
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取回调函数,此时就获取到了咱们在创立 A 时放入的回调函数
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用回调办法获取晚期 bean,因为咱们当初探讨的是一般对象,所以返回原对象
singletonObject = singletonFactory.getObject();
// 将晚期对象放到二级缓存,移除三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
// 返回晚期对象 A
return singletonObject;
}
震惊!此时咱们就拿到了 A 的晚期对象进行返回,所以 B 得以被填充属性,B 创立结束后,又将返回到 A 填充属性的过程,A 也得以被填充属性,A 也创立结束,这时,A 和 B 都创立好了,循环依赖问题得以开场~
一般 Bean 和一般 Bean 之间的问题就到这里了,不晓得小伙伴们有没有晕呢~
2. 一般 Bean 和代理对象
一般 Bean 和代理对象之间的循环依赖与两个一般 Bean 的循环依赖其实大致相同,只不过是多了一次动静代理的过程,咱们假如 A 对象是须要代理的对象,B 对象依然是一个一般对象,而后,咱们开始创立 A 对象。
刚开始创立 A 的过程与下面的例子是截然不同的,紧接着天然是须要创立 B,而后 B 依赖了 A,于是又倒回去创立 A,此时,再次走到去缓存池获取的过程。
// 从三级缓存中获取回调函数
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用回调办法获取晚期 bean,此时返回的是一个 A 的代理对象
singletonObject = singletonFactory.getObject();
// 将晚期对象放到二级缓存,移除三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
这时就不太一样了,在 singletonFactory.getObject()
时,因为此时 A 是须要代理的对象,在调用回调函数时,就会触发动静代理的过程
AbstractAutoProxyCreator#getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) {
// 生成一个缓存 Key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 放入缓存中,用于在初始化后调用该后置处理器时判断是否进行动静代理过
this.earlyProxyReferences.put(cacheKey, bean);
// 将对象进行动静代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
此时,B 在创立时填充的属性就是 A 的代理对象了,B 创立结束,返回到 A 的创立过程,但此时的 A 依然是一个一般对象,可 B 援用的 A 曾经是个代理对象了,不晓得小伙伴看到这里有没有蛊惑呢?
不急,让咱们持续往下走,填充完属性天然是须要初始化的,在初始化后,会调用一次后置处理器,咱们看看会不会有答案吧
初始化
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
//... 省略后面的步骤...
// 调用初始化办法
invokeInitMethods(beanName, wrappedBean, mbd);
// 解决初始化后的 bean
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
在解决初始化后的 bean,又会调用动静代理的后置处理器了
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 判断缓存中是否有该对象,有则阐明该对象已被动静代理,跳过
if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
不晓得小伙伴发现没有,earlyProxyReferences
这个缓存可不就是咱们在填充 B 的属性,进而从缓存中获取 A 时放进去的吗?不信您往上翻到 getEarlyBeanReference
的步骤看看~
所以,此时并未进行任何解决,仍旧返回了咱们的原对象 A,看来这里并没有咱们要的答案,那就持续吧~
// 是否容许裸露晚期对象
if (earlySingletonExposure) {
// 从缓存池中获取晚期对象
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// bean 为初始化前的对象,exposedObject 为初始化后的对象
// 判断两对象是否相等,基于下面的剖析,这两者是相等的
if (exposedObject == bean) {
// 将晚期对象赋给 exposedObject
exposedObject = earlySingletonReference;
}
}
}
咱们来剖析一下下面的逻辑,getSingleton
从缓存池中获取晚期对象返回的是什么呢?
synchronized (this.singletonObjects) {
// 从晚期对象缓存池中获取,此时就拿到了咱们填充 B 属性时放入的 A 的代理对象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从三级缓存中获取回调函数
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用回调办法获取晚期 bean
singletonObject = singletonFactory.getObject();
// 将晚期对象放到二级缓存,移除三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
发现了吗?此时咱们就获取到了 A 的代理对象,而后咱们又把这个对象赋给了 exposedObject,此时创建对象的流程走完,咱们失去的 A 不就是个代理对象了吗~
此次栗子是先创立须要代理的对象 A,假如咱们先创立一般对象 B 会产生什么呢?
3. 代理对象与代理对象
代理对象与代理对象的循环依赖是怎么样的呢?解决过程又是如何呢?这里就留给小伙伴本人思考了,其实和一般 Bean 与代理对象是截然不同的,小伙伴想想是不是呢,这里我就不做剖析了。
4. 一般 Bean 与工厂 Bean
这里所说的一般 Bean 与工厂 Bean 并非指 bean 与 FactoryBean,这将毫无意义,而是指一般 Bean 与 FactoryBean 的 getObject
办法产生了循环依赖,因为 FactoryBean
最终产生的对象是由 getObject
办法所产出。咱们先来看看栗子吧~
假如工厂对象 A 依赖一般对象 B,一般对象 B 依赖一般对象 A。
小伙伴看到这里就可能问了,诶~你这不对呀,怎么成了「一般对象 B 依赖一般对象 A」呢?不应该是工厂对象 A 吗?是这样的,在 Spring 中,因为一般对象 A 是由工厂对象 A 产生,所有在一般对象 B 想要获取一般对象 A 时,其实最终寻找调用的是工厂对象 A 的 getObject
办法,所以只有一般对象 B 依赖一般对象 A 就能够了,Spring 会主动帮咱们把一般对象 B 和工厂对象 A 分割在一起。
小伙伴,哦~
一般对象 A
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {this.normalBeanB = normalBeanB;}
}
工厂对象 A
@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
@Autowired
private ApplicationContext context;
@Override
public NormalBeanA getObject() throws Exception {NormalBeanA normalBeanA = new NormalBeanA();
NormalBeanB normalBeanB = context.getBean("normalBeanB", NormalBeanB.class);
normalBeanA.setNormalBeanB(normalBeanB);
return normalBeanA;
}
@Override
public Class<?> getObjectType() {return NormalBeanA.class;}
}
一般对象 B
@Component
public class NormalBeanB {
@Autowired
private NormalBeanA normalBeanA;
}
假如咱们先创建对象 A
因为 FactoryBean 和 Bean 的创立过程是一样的,只是多了步 getObject
,所以咱们间接定位到调用getObject
入口
if (mbd.isSingleton()) {
// 开始创立 bean
sharedInstance = getSingleton(beanName, () -> {
// 创立 bean
return createBean(beanName, mbd, args);
});
// 解决 FactoryBean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 先尝试从缓存中获取,保障屡次从工厂 bean 获取的 bean 是同一个 bean
object = getCachedObjectForFactoryBean(beanName);
if (object == null) {
// 从 FactoryBean 获取对象
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
}
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 加锁,避免多线程时反复创立 bean
synchronized (getSingletonMutex()) {
// 这里是 Double Check
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {// 获取 bean,调用 factoryBean 的 getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
}
// 又从缓存中取了一次,why? 咱们缓缓剖析
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {object = alreadyThere;}else{
// ... 省略初始化 bean 的逻辑...
// 将获取到的 bean 放入缓存
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName){return factory.getObject();
}
当初,就走到了咱们自定义的 getObject
办法,因为咱们调用了 context.getBean("normalBeanB", NormalBeanB.class)
,此时,将会去创立 B 对象,在创立过程中,先将 B 的晚期对象放入三级缓存,紧接着填充属性,发现依赖了 A 对象,又要倒回来创立 A 对象,从而又回到下面的逻辑,再次调用咱们自定义的getObject
办法,这个时候会产生什么呢?
又要去创立 B 对象 …(Spring: 心好累)
然而!此时咱们在创立 B 时,是间接通过 getBean
在缓存中获取到了 B 的晚期对象,得以返回了!于是咱们自定义的 getObject
调用胜利,返回了一个残缺的 A 对象!
然而此时 FactoryBean
的缓冲中还是什么都没有的。
// 又从缓存中取了一次
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {object = alreadyThere;}
这一次取 alreadyThere
必然是 null,流程继续执行,将此时将获取到的 bean 放入缓存
this.factoryBeanObjectCache.put(beanName, object);
从 FactoryBean 获取对象的流程完结,返回到创立 B 的过程中,B 对象此时的属性也得以填充,再返回到第一次创立 A 的过程,也就是咱们第一次调用自定义的 getObject
办法,调用结束,返回到这里
// 获取 bean,调用 factoryBean 的 getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {object = alreadyThere;
那么,此时 this.factoryBeanObjectCache.get(beanName)
能从缓冲中拿到对象了吗?有没有发现,拿到了刚刚 B 对象填充属性时再次创立 A 对象放进去的!
所以,明确这里为什么要再次从缓存中获取了吧?就是为了解决因为循环依赖时调用了两次自定义的 getObject
办法,从而创立了两个不雷同的 A 对象,保障咱们返回进来的 A 对象惟一!
怕小伙伴晕了,画个图给大家
5. 工厂 Bean 与工厂 Bean 之间
咱们曾经举例 4 种循环依赖的栗子,Spring 都有所解决,那么有没有 Spring 也无奈解决的循环依赖问题呢?
有的!就是这个 FactoryBean
与FactoryBean
的循环依赖!
假如工厂对象 A 依赖工厂对象 B,工厂对象 B 依赖工厂对象 A,那么,这次的栗子会是什么样呢?
一般对象
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {this.normalBeanB = normalBeanB;}
}
public class NormalBeanB {
private NormalBeanA normalBeanA;
public void setNormalBeanA(NormalBeanA normalBeanA) {this.normalBeanA = normalBeanA;}
}
工厂对象
@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
@Autowired
private ApplicationContext context;
@Override
public NormalBeanA getObject() throws Exception {NormalBeanA normalBeanA = new NormalBeanA();
NormalBeanB normalBeanB = context.getBean("factoryBeanB", NormalBeanB.class);
normalBeanA.setNormalBeanB(normalBeanB);
return normalBeanA;
}
@Override
public Class<?> getObjectType() {return NormalBeanA.class;}
}
@Component
public class FactoryBeanB implements FactoryBean<NormalBeanB> {
@Autowired
private ApplicationContext context;
@Override
public NormalBeanB getObject() throws Exception {NormalBeanB normalBeanB = new NormalBeanB();
NormalBeanA normalBeanA = context.getBean("factoryBeanA", NormalBeanA.class);
normalBeanB.setNormalBeanA(normalBeanA);
return normalBeanB;
}
@Override
public Class<?> getObjectType() {return NormalBeanB.class;}
}
首先,咱们开始创建对象 A,此时为调用工厂对象 A 的 getObject
办法,转而去获取对象 B,便会走到工厂对象 B 的 getObject
办法,而后又去获取对象 A,又将调用工厂对象 A 的 getObject
,再次去获取对象 B,于是再次走到工厂对象 B 的getObject
办法 …… 此时,已经验了一轮循环,却没有跳出循环的迹象,妥妥的死循环了。
咱们画个图吧~
没错!这个图就是这么简略,因为始终无奈创立出一个对象,不论是晚期对象或者残缺对象,使得两个工厂对象重复的去获取对方,导致陷入了死循环。
那么,咱们是否有方法解决这个问题呢?
我的答案是无奈解决,如果有想法的小伙伴也能够本人想一想哦~
咱们发现,在产生循环依赖时,只有循环链中的某一个点能够先创立出一个晚期对象,那么在下一次循环时,就会使得咱们可能获取到晚期对象从而跳出循环!
而因为工厂对象与工厂对象间是无奈创立出这个晚期对象的,无奈满足跳出循环的条件,导致变成了死循环。
那么此时 Spring 中会抛出一个什么样的异样呢?
当然是栈溢出异样啦!两个工厂对象始终互相调用,一直开拓栈帧,可不就是栈溢出有木有~
6. 工厂对象与代理对象
下面的状况是无奈解决循环依赖的,那么这个状况能够解决吗?
答案是能够的!
咱们剖析了,一个循环链是否可能失去终止,关键在于是否可能在某个点创立出一个晚期对象(长期对象),而代理对象在 doCreateBean
时,是会生成一个晚期对象放入三级缓存的,于是该循环链得以终结。
具体过程我这里就不再细剖析了,就交由小伙伴本人入手吧~
总结
以上咱们一共举例了 6 种状况,通过剖析,总结出这样一条定律:
在产生循环依赖时,判断一个循环链是否可能失去终止,关键在于是否可能在某个点创立出一个晚期对象(长期对象),那么在下一次循环时,咱们就能通过该晚期对象进而跳出(突破)循环!
通过这样的定律,咱们得出工厂 Bean 与工厂 Bean 之间是无奈解决循环依赖的,那么还有其余状况无奈解决循环依赖吗?
有的!以上的例子举的都是单例的对象,并且都是通过 set 办法造成的循环依赖。
倘若咱们是因为构造方法造成的循环依赖呢?是否有解决办法吗?
没有,因为这并不满足咱们得出的定律
无奈执行结束构造方法,天然无奈创立出一个晚期对象。
倘若咱们的对象是多例的呢?
也不能,因为多例的对象在每次创立时都是创立新的对象,即便可能创立出晚期对象,也不能为下一次循环所用!
好了,本文就到这里完结了,心愿小伙伴们有所播种~
Spring IOC 的外围局部到此篇就完结了,下一篇就让咱们进行 AOP 之旅吧~
下文预报:Spring 源码剖析之 AOP 从解析到调用
Spring 源码系列
- Spring 源码剖析之 IOC 容器预启动流程(已完结)
- Spring 源码剖析之 BeanFactory 体系结构(已完结)
- Spring 源码剖析之 BeanFactoryPostProcessor 调用过程(已完结)
- Spring 源码剖析之 Bean 的创立过程(已完结)
- Spring 源码剖析之什么是循环依赖及解决方案
- Spring 源码剖析之 AOP 从解析到调用
- Spring 源码剖析之事务管理(上),事物治理是 spring 作为容器的一个特点,总结一下他的根本实现与原理吧
- Spring 源码剖析之事务管理(下),对于他的底层事物隔离与事物流传原理,重点剖析一下
Spring Mvc 源码系列
- SpringMvc 体系结构
- SpringMvc 源码剖析之 Handler 解析过程
- SpringMvc 源码剖析之申请链过程
另外笔者公众号:奇客工夫,有更多精彩的文章,有趣味的同学,能够关注