1. 前言
Spring在较新版本中已经默认不允许bean之间发生「循环依赖」了,如果检测到循环依赖,容器启动时将会报错,此时可以通过配置来允许循环依赖。
spring.main.allow-circular-references=true
什么是循环依赖?
循环依赖也叫循环引用,简单点说,就是bean之间的依赖关系形成了一个循环,例如beanA依赖beanB,beanB又依赖beanA。
@Component
public class A {
@Autowired
B b;
}
@Component
public class A {
@Autowired
B b;
}
如上代码所示,Spring本身是支持循环依赖的,Spring加载bean时,首先会实例化A,然后对A做初始化,其中就包含属性填充,填充属性时发现A依赖B,于是Spring又会从容器中去加载B,创建B时发现B又依赖了A,循环依赖就此产生,Spring是如何打破这个循环的呢?
2. 依赖注入的方式
Spring完成依赖注入的方式有三种:
对于构造函数注入的方式,循环依赖是无解的,Spring也无法支持。属性注入和Setter方法注入原理上是一样的,本文通过属性注入的方式来分析Spring是如何支持循环依赖的。
循环依赖仅限于单例bean,对于原型bean,循环依赖也是不允许的。
3. bean的多级缓存
Spring支持循环依赖的核心是bean的三级缓存。
Spring会在启动的时候,加载容器内所有非lazy的单例bean,方法是DefaultListableBeanFactory#preInstantiateSingletons()
,该方法会把容器内所有非lazy的单例beanName拿出来,然后依次调用getBean()
方法获取bean。
getBean()
时,首先会调用getSingleton()
方法优先从缓存中获取bean,只要缓存中存在bean,就可以直接获取了,避免二次创建。
核心就在getSingleton()
方法中,Spring会在各级缓存中查找bean。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 检查一级缓存,是否存在bean
Object singletonObject = this.singletonObjects.get(beanName);
// bean是否创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
/**
* 二级缓存查找
* 二级缓存意义不大?没有二级缓存也能解决循环依赖,作用是啥?
* 三级缓存的ObjectFactory#getObject()方法是有代价的,会触发后置处理器获取早期引用
* @see SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
* 频繁触发势必带来性能问题,引入二级缓存,一旦getObject()触发就移入二级缓存
*/
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 三级缓存中查找
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
该方法做了几件事:
所谓的多级缓存,其实就是三个Map容器。
/**
* 一级缓存,完整的bean容器
* 经历了完整生命周期的bean
*/
private final Map singletonObjects = new ConcurrentHashMap(256);
/**
* 二级缓存,提前暴露的bean,只实例化了,还未初始化
*/
private final Map earlySingletonObjects = new HashMap(16);
/**
* 三级缓存,不直接缓存bean
* 而是提前暴露的bean的工厂方法,调用方法来产生bean
* 对于需要AOP增强的bean,此时就需要生成带来对象了
*/
private final Map