循环依赖
什么是循环依赖
Spring一个很重要的能力就是依赖注入,即可以通过注解、xml等方式标识应用在创建的时候将依赖的bean注入到属性中去,这个过程是发生在对象创建过程中的,大致是当A实例化的时候,需要依赖B实例注入,但是此时B还未实例化,因此要先实例化B,然后将B实例引用注入到A的属性中去,然后继续初始化A,从而实现了A实例创建和依赖注入。
从上面草图可以看到一个在依赖其它实例的对象初始化过程,但是上面会存在一个问题,就是实例化B的时候,如果B也依赖A怎么办,如下图:
当两个实例互相依赖的时候就会导致产生循环依赖,进入死循环无法创建对象。但是Spring是解决了这个问题的,实际运行中以下这个代码是能够正常工作的。
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; } // 测试程序 public static void main(String[] args) { AnnotationConfigApplicationContext annotationApp = new AnnotationConfigApplicationContext(RecycleDIDemo.class)e); A a = annotationApp.getBean(A.class); B b = annotationApp.getBean(B.class); System.out.println(); }
解决循环依赖
Spring默认情况下开启了循环依赖处理,如果要关闭可以通过使用ApplicationContext的setAllowCircularReferences方法进行关闭循环依赖,关闭循环依赖后刷新容器将会抛出异常
BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
public static void main(String[] args) { AnnotationConfigApplicationContext annotationApp = new AnnotationConfigApplicationContext(); annotationApp.setAllowCircularReferences(false); annotationApp.register(RecycleDIDemo.class); // 设置完setAllowCircularReferences属性后再刷新容器,防止带参数的构造器自动刷新容器以至于后面设置后不生效 annotationApp.refresh(); A a = annotationApp.getBean(A.class); B b = annotationApp.getBean(B.class); System.out.println(); }
原理
Spring之所以能够解决循环依赖主要是采用了一种置换原则,也就是俗称的三级缓存,其实就是三个Map。
简单说下原理:
当A创建的时候,发现容器中还不存在B的实例,于是将A存放到一个暂存Map中,然后转头去创建B,创建B的时候发现A不在容器中,此时就去暂存Map中找,能够找到A(
注意此时A已经实例化了,但是还没有初始化,也就是说暂存Map中已经有了A的引用,只是不完整);然后将A的引用赋值给B的属性,此时B完成创建,丢入容器,回到A创建逻辑此时能够在容器中获取到B的实例,因此可以完成属性赋值完成A的初始化,解决循环依赖。
原理分析完了,看下具体实现,在Spring中注册单例Bean有三个Map,位于DefaultSingletonBeanRegistry类中,分别是:
Map<String, Object> singletonObjects :存储已经初始化完毕可以使用的bean;
Map<String, Object> earlySingletonObjects:存储已经创建了实例,但是还没有完成初始化的bean;
Map<String, ObjectFactory<?>> singletonFactories:还未创建对象实例,存储创建对象的工厂。
按照顺序,分别称上面是哪个缓存为一、二、三级缓存。
具体工作如下图所示:
图片来源:www.zhihu.com/question/43…
源码分析
先来简单看一下Spring Bean在创建初始化的整个过程
AbstractApplicationContext#refresh(入口) AbstractApplicationContext#finishBeanFactoryInitialization(初始化单例对象入口) ConfigurableListableBeanFactory#preInstantiateSingletons(初始化单例对象入口) AbstractBeanFactory#getBean(java.lang.String)(万恶之源,获取并创建Bean的入口) AbstractBeanFactory#doGetBean(实际的获取并创建Bean的实现)
DefaultSingletonBeanRegistry#getSingleton(java.lang.String)(从缓存中尝试获取) AbstractAutowireCapableBeanFactory#createBean(java.lang.String, pport.RootBeanDefinition, java.lang.Object[])(实例化Bean) AbstractAutowireCapableBeanFactory#doCreateBean(实例化Bean具体实现) AbstractAutowireCapableBeanFactory#createBeanInstance(具体实例化过程) DefaultSingletonBeanRegistry#addSingletonFactory(将实例化后的Bean添加到三级缓存) AbstractAutowireCapableBeanFactory#populateBean(实例化后属性注入) AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, pport.RootBeanDefinition)(初始化入口)DefaultSingletonBeanRegistry#addSingleton(创建完成放入一级缓存)
在上面调用链中我们主要关心和解决依赖注入的就是几个加粗重要方法。
doGetBean获取bean实例,如果获取不到则使用默认beanFactory创建bean,也就是继续第②步getSingleton方法,最后会进入到具体创建bean的方法AbstractAutowireCapableBeanFactory#doCreateBean中
sharedInstance = this.getSingleton(beanName, () -> {
try {
return this.createBean(beanName, mbd, args);
} catch (BeansException var5) {
this.destroySingleton(beanName);
throw var5;
}
});
为什么设置了setAllowCircularReferences(false)就无法解决循环依赖了
在AbstractAutowireCapableBeanFactory#doCreateBean中有一点要值得关注的就是
// 判断是否需要放入三级缓存,条件:bean是单例的,开启了循环依赖(allowCircularReferences),并且在创建中
boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
}
// 添加到三级缓存中
this.addSingletonFactory(beanName, () -> {
// 获取对象的默认实现
return this.getEarlyBeanReference(beanName, mbd, bean);
});
}
以上的代码就解释了为什么setAllowCircularReferences(false)后,就无法启用循环依赖机制了。
依赖注入populateBean
在AbstractAutowireCapableBeanFactory#doCreateBean中,添加完三级缓存后就会执行依赖注入,入口就是populateBean方法。
注入方式有很多,具体看InstantiationAwareBeanPostProcessor的实现类,像我们使用的@Autowired注解,就会执行AutowiredAnnotationBeanPostProcessor的方法完成注入。
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
PropertyValues pvsToUse = ibp.postProcessProperties((PropertyValues)pvs, bw.getWrappedInstance(), beanName);
.....
}
AutowiredAnnotationBeanPostProcessor#postProcessProperties
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
return pvs;
} catch (BeanCreationException var6) {
throw var6;
} catch (Throwable var7) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);
}
}
最后可以看到注入的具体实现分别有着三个
字段,方法注入,这里就很好的证明了@Autowired能够在哪里执行自动注入属性
获取创建中对象引用
// DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
synchronized(this.singletonObjects) {
// 从二级缓存中获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
// 从三级缓存中获取对象,放入二级缓存
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
注意这里的getObject方法跟一开始的AbstractAutowireCapableBeanFactory#doCreateBean是要结合看,在doCreateBean的时候,创建的默认beanfactory的返回中实际调用的是getEarlyBeanReference(beanName, mbd, bean);方法来获取到最终实例的引用的。
最后populateBean注入结束后就会执行初始化方法,进一步完成对象创建。
清理二三级缓存,完成对象创建
在完成以上步骤后,DefaultSingletonBeanRegistry#getSingleton(java.lang.String)方法就算执行完了,接下来就会执行
// 使用局部变量标识当前是新建对象
if (newSingleton) {
this.addSingleton(beanName, singletonObject);
}
DefaultSingletonBeanRegistry#addSingleton(创建完成放入一级缓存)
protected void addSingleton(String beanName, Object singletonObject) {
synchronized(this.singletonObjects) {
// 将完整对象放入一级缓存
this.singletonObjects.put(beanName, singletonObject);
// 移除二三级缓存
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
// 记录已经注册好的beanName
this.registeredSingletons.add(beanName);
}
}
这时候放入一级缓存的对象就是完整的对象实例,可以使用,并且会清除掉二三级缓存。
小总结
从以上源码可以分析得出,当对象第一次被getSingleton的时候是从singletonObject = singletonFactory.getObject()获取的并放入二级缓存,此时获取到的对象并不一定是完整的对象,只能说它是一个实际后面使用的实例,并不代表对象已经创建完成;最后放入一级缓存的才是完成的实例。
为什么要这样
只有一级缓存SingletonObjects能否解决循环依赖,如果能解决,会有什么问题?
如果只有一级缓存也能解决循环依赖的问题,不过只能在单线程的情况下,不支持多线程。
通过上面分析到,解决循环依赖的核心思路是利用一个中间缓存来保存一个创建中对象。
一级缓存实现思路:A->B,B->A。
1. A创建,初始化依赖B,去一级缓存中查找B,B不存在
2. A放入一级缓存SingletonObjects后执行创建B
3. B创建,初始化依赖A,去一级缓存中查找A,查找到A将引用赋值给属性
4. 完成B创建,将B放入一级缓存中,继续走A的初始化逻辑
5. 这时候B已经在一级缓存了,因此直接将B的引用赋值给A,完成A的创建
以上就是通过一个中间缓存来解决了“单纯”的循环依赖解决方案。
但是上面方案是一种理想状态,如果在application初始化的时候,还有其它线程来获取对象就会造成获取到一个半成品对象的问题,如在上面步骤3执行后,立马一个线程来获取A对象,则会获取到一个A的半成品,这显然是不行的。
只有一、二级缓存SingletonObjects和earlySingletonObjects能否解决循环依赖,如果能解决,会有什么问题?
上面只有一级缓存我们分析出有线程安全的问题,那么用二级缓存来处理,一级缓存永远是创建好的实例,二级缓存为半成品。
一、二级缓存实现思路:A->B,B->A。
1 A创建,初始化依赖B,去一级缓存中查找B,B不存在,再去二级缓存中查找B,B还是不存在
2 A放入二级缓存earySingletonObjects后执行创建B
3 B创建,初始化依赖A,去一级缓存中查找A,A不存在,去二级缓存中查找到A将引用赋值给属性
4 完成B创建,将B放入一级缓存中,继续走A的初始化逻辑
5 A从一级缓存获取到B,完成A的依赖赋值,完成A的创建,A放入一级缓存
6 完成A的创建
从上面可以看到使用二级缓存能够解决循环依赖问题和多线程获取的问题,理论上来说没有问题。
但是从上面流程看到缓存的都是具体的实例对象,加入A和B都是需要代理的对象,则在上面流程中就满足不了,因为A和B都是生成的具体实例,生成后还需要代理,代理的对象引用和实例对象不是同一个,因此光存储一个Object不能满足要求。
doCreateBean->getSingletonObject是属于Bean实例化阶段,而代理是在Bean的初始化阶段之后,因此只能通过ObjectFactors提前暴露。
看简版源码:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // 创建bean实例 instanceWrapper = this.createBeanInstance(beanName, mbd, args); Object bean = instanceWrapper.getWrappedInstance(); // 暴露出去的对象 Object exposedObject = bean; // 放入三级缓存 boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName); if (earlySingletonExposure) { if (this.logger.isTraceEnabled()) { this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } this.addSingletonFactory(beanName, () -> { return this.getEarlyBeanReference(beanName, mbd, bean); }); } Object exposedObject = bean; // 属性注入 this.populateBean(beanName, mbd, instanceWrapper); // 初始化 exposedObject = this.initializeBean(beanName, exposedObject, mbd); Object earlySingletonReference = this.getSingleton(beanName, false); if (earlySingletonReference != null) { // 暴露出去的对象结果为getSingleton的最终结果 if (exposedObject == bean) { exposedObject = earlySingletonReference; } }根据以前分析BeanPostProcessor的时候,我们知道了创建代理对象是在对象初始化initializeBean调用applyBeanPostProcessorsAfterInitialization的时候,进行创建的代理对象,也就是说如果在instanceWrapper = this.createBeanInstance(beanName, mbd, args)创建完就立马放入二级缓存,那么在populateBean的时候,循环依赖当前的对象则会拿到一个还没有执行initializeBean方法的bean引用,不是被代理后的对象了。这也就是为什么三级缓存存在的原因。
并且看以下代码也能看出,最后返回给容器使用的也是需要调用getSingleton的最终结果,而不是直接返回了最开始实例化instanceWrapper = this.createBeanInstance(beanName, mbd, args);的对象,这个对象工厂getEarlyBeanReference(beanName, mbd, bean);
Object earlySingletonReference = this.getSingleton(beanName, false); if (earlySingletonReference != null) { // 暴露出去的对象结果为getSingleton的最终结果 if (exposedObject == bean) { exposedObject = earlySingletonReference; }
三级缓存ObjectFactors为什么是一个工厂,而不是像其它两个缓存一样直接存对象?
三级缓存主要就是为了解决上面二级缓存无法解决的问题,需要获取的对象是需要增强的不单单是一个Object,当需要获取依赖的时候实时创建,创建代理通过getEarlyBeanReference()方法获取代理对象的引用。 典型就是AOP的代理中大量的BeanPostProcesser,如AbstractAutoProxyCreator#getEarlyBeanReference。
这里具体就要看AbstractAutoProxyCreator的源码了。
按照正常逻辑,创建代理对象会在BeanPostProcessor的时候才创建,但是就是因为对象工厂getObject的缘故,在循环依赖的情况下,对象代理会在依赖它的实例populateBean的时候就会创建,如果不是工厂就不具备这种动态调整的能力。
为什么一定要三级缓存,使用一级(SingletonObjects)+三级缓存(SingletonFactories)是否能够满足要求?
咋然一看这个好像也能解决问题,为什么还要二级缓存,二级缓存主要功能是用来保持后面使用的对象实例引用的唯一性。因为对象工厂每次getObject的时候都会创建一个新的Object违反了单例原则。
成品放在singletonObjects中,半成品通过singletonFactories来获取
- 实例化A ->创建A的对象工厂并放入singletonFactories中
- 填充A的属性时发现取不到B->实例化B->创建B的对象工厂并放入singletonFactories中
- 从singletonFactories中获取A的对象工厂并获取A填充到B中
- 将成品B放入singletonObjects,并从singletonFactories中删除B的对象工厂
- 将B填充到A的属性中->将成品A放入singletonObjects并删除A的对象工厂 上面会有两个问题,第一个是SingletonFactories每次调用getObject都会创建一个新的对象,在多个实例依赖它的时候,就会出现多个实例依赖的类实例引用不是单例的,这也就是为什么要将三级缓存生成的对象引用放入二级缓存。
什么情况下循环依赖会失效
关闭循环依赖:
注意,Spring的循环依赖处理是可以关闭的,关闭循环依赖的好处可以规范代码单向依赖,能够提高代码规范。 AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(); context.register(AppConfig.class); context.setAllowCircularReferences(false);// 关闭循环依赖 context.refresh(); SchoolService school = context.getBean(SchoolService.class);
原型注入:
当一个单例注入一个带有循环依赖的原型Bean的时候,这时候在应用启动的时候就会报错,因为从源码就知道DefaultSingletonBeanRegistry#getSingeton处理循环依赖和创建,该类是只支持单例的,处理原型的时候原型对象没有用到缓存因此无法解决循环依赖。
构造器注入:
因为构造器在对象实例化的时候就要调用,即就需要注入,这时候都没有走到poputleBean自然也没有用单三个缓存因此无法注入;但是Spring提供了一个@Lazy来解决这个问题,先注入一个代理对象。
使用DependsOn循环依赖
相互用DependsOn注解标注预先加载对方也会产生循环依赖
避免循环依赖的手段
生成代理对象产生的循环依赖这类循环依赖问题解决方法很多,主要有:
使用@Lazy注解,延迟加载,可以解决构造器循环依赖的问题
使用@DependsOn注解,指定加载先后关系
修改文件名称,改变循环依赖类的加载顺序
使用@DependsOn产生的循环依赖这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题。
多例循环依赖这类循环依赖问题可以通过把bean改成单例的解决。
构造器循环依赖这类循环依赖问题可以通过使用@Lazy注解解决。
使用ApplicationAware和InitializeBean搭配使用,手动注入。