往期文章:
- Spring源码分析之预启动流程
- Spring源码分析之BeanFactory体系结构
- Spring源码分析之BeanFactoryPostProcessor调用过程详解
- Spring源码分析之Bean的创建过程详解
正文:
首先,我们需要明白什么是循环依赖?简单来说就是A对象创建过程中需要依赖B对象,而B对象创建过程中同样也需要A对象,所以A创建时需要先去把B创建出来,但B创建时又要先把A创建出来…死循环有木有…
循环依赖.png
那么在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都创建好了,循环依赖问题得以收场~
解决过程.png
普通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会发生什么呢?