首先,我们先明确下依赖的定义。 如果一个 Bean bar 的属性,引用了容器中的另外一个 Bean foo,那么称 foo 为 bar 的依赖,或称 bar 依赖 foo。 如果用代码表示,可以表示为:
@Component("foo")
public Class Foo {
@Autowired
private Bar bar; // 称 foo 依赖 bar
}
@Component("bar")
public Class Bar {
@Autowired
private Baz baz; // bar 依赖 baz
}
复制代码
其次,循环引用的概念是指多个 Bean 之间的依赖关系形成了环。 接着上面的例子,如果 baz 依赖 foo 或 bar 都将形成循环依赖。
@Component("baz")
public class Baz {
@Autowired
private Foo foo; // 形成了循环依赖,baz -> (依赖) foo -> bar -> baz ...
}
复制代码
我跟大家一块学习了 Spring 创建 Bean 过程的源码。 我们知道:在 createBeanInstance 阶段,需要解决构造器、工厂方法参数的依赖; 在 populateBean 阶段,需要解决类属性中对其他 Bean 的依赖。 这其实对应了 Spring 中支持的两种依赖注入方式,基于构造器的依赖注入和基于 setter 方法的依赖注入,分别对应前面的两种情况。
接下来,我会通过上节介绍的示例,来分情况讨论产生循环依赖的场景。 为了使讨论过程更清楚、更简洁,我会让 foo 依赖 bar,而 bar 依赖 foo。 在接下来的描述中,我假设 Spring 会先创建 Bar 的对象,再创建 Foo 的对象。 针对不同的依赖情况,可以分为四种场景:
在具体分析上述四种场景之前,先说下结论: Spring 可以解决场景三、四中出现循环依赖的情况,而第一、二种场景,Spring 无法解决,需要重构依赖或者延迟延迟依赖注入的时机(例如使用 @Lazy 等)。 细心的读者可能会问第二种、第三种场景有什么不同呢? 其实第二、第三种场景本质上是同一种情况,唯一的不同是实例化的先后顺序。 结合这个信息,可以得出,先创建的类以构造器参数方式依赖其他 Bean,则会发生循环依赖异常。 反过来,如果先创建的类以 setter 方式依赖其他 Bean,则不会发生循环依赖异常。
接下来,我会详细分析每一种场景,并指出抛循环依赖异常的时机。 (注:接下来的分析需要对 Spring 创建 Bean 的过程有基本的了解
首先,所有的单例 Bean 会在容器启动后被创建 ConfigurableListableBeanFactory#preInstantiateSingletons,即所谓的 “eager registration of singletons” 过程。
第一种场景,会先触发 Bar 类的实例 bar 的创建。在 createBeanInstance 阶段,会通过 ConstructorResolver#autowireConstructor 来创建实例。 ConstructorResolver#createArgumentArray 会解析构造器中的参数,并处理对其他 Bean 依赖的引用 ConstructorResolver#resolveAutowiredArgument。 处理依赖的方式就是通过 DefaultListableBeanFactory#resolveDependency 来查找符合条件的 Bean,最终还是通过 AbstractBeanFactory#getBean 来从容器中取。 当通过 getBean("bar") 来触发 Spring 创建 bar 时,在实例化阶段,根据构造器参数来 getBean("foo") 并触发 foo 的创建。 在 foo 的实例化过程与 bar 的是完全一样的,最终 getBean("bar")。这是容器中的 bar 还没有创建好,所以会再次触发创建过程。 在真正创建过程之前,在 DefaultSingletonBeanRegistry#getSingleton 中会有一次检查,DefaultSingletonBeanRegistry#beforeSingletonCreation 如果发现要创建的 bean 正在创建过程中,则抛出异常。
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'bar': Requested bean is currently in creation: Is there an unresolvable circular reference?
复制代码
第二种场景,同样先构建 bar 实例。与第一种场景不同之处在于 foo 创建时,它的 createBeanInstance 阶段能够执行完毕。 原因是 foo 只有一个无参构造器(即默认构造器),不需要注入其他依赖。 foo 的 createBeanInstance 阶段执行完毕后,会进入 populateBean 阶段。 在这个阶段中,AutowiredAnnotationBeanPostProcessor#postProcessProperties 会处理 setter 函数依赖的 Bean。 大致处理过程为:AutowiredAnnotationBeanPostProcessor 识别到 foo 中包含需要注入依赖的 setter 函数,将其映射为 AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement 对象。 然后调用 AutowiredMethodElement#inject 方法注入依赖。 在 inject 方法中,会调用 DefaultListableBeanFactory#resolveDependency 来查找对应的依赖。 到这里为止,后续的过程与第一种场景完全一致了。 从容器中尝试获取 bar,发现不存在,会出发 bar 的再次创建,最终在 DefaultSingletonBeanRegistry#beforeSingletonCreation 中抛出异常。
第三种场景,同样先构建 bar 实例。由于它只包含一个默认构造器,所以它的 createBeanInstance 阶段会顺利完成,然后进入 populateBean 阶段。 当你仔细回看一下 Spring 创建 Bean 过程的源码,你会发现下面这段代码:
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
复制代码
这段逻辑发生在 createBeanInstance 之后,尚未进入 populateBean 之前。 这里其实就是 Spring 解决循环依赖机制的核心点之一,这里我暂且不深入介绍,后面会有详细的分析。
继续前面的分析。bar 实例创建进入到 populateBean 阶段后,会检查其自身依赖情况,然后注入对应的依赖 Bean。 这里的处理逻辑依然是 AutowiredAnnotationBeanPostProcessor#postProcessProperties 来处理。 当尝试注入 foo 时,会出发 foo 实例的创建过程。foo 通过构造器依赖 bar,因此在其 createBeanInstance 阶段,会通过 ConstructorResolver#autowireConstructor 完成依赖注入。 此时通过 getBean("bar") 从容器中尝试获取 bar 时,能够“获取到”。 注:这里为什么不会出发 bar 的创建,反而能够直接得到 bar 对象呢?上面的获取到我加了引号,它其实获得的并不是一个完整、可用的 bar。 它获得的是通过 earlySingletonExposure 提前暴露出的对象。 这个过程在后面介绍三级缓存时会详细介绍。
篇幅原因,第四种场景我不在这里继续分析,感兴趣的读者可以自己尝试分析下。 简单提示下,它的过程有点像第三种场景前半段、第二种场景的后半段结合起来。
在上述四种场景下,第一种情况,依赖双方都是通过构造器依赖对方,这种情况下 Spring 是无法处理的。 而且,我认为出现这一情况,属于是设计上的缺陷,应当通过重新设计依赖关系来解决,例如可以将基于构造器的注入修改为基于 setter 的注入,或者通过 @Lazy 将依赖的初始化延迟到使用时。 通过 Foo、Bar 类来举例说明。
@Component
public class Foo {
private Bar bar;
@Autowired
public Foo(@Lazy Bar bar) {
this.bar = bar;
}
}
@Component
public class Bar {
private Foo foo;
@Autowired
public Bar(Foo foo) { // 或者将对 foo 的依赖,注解为 @Lazy 表示使用时才初始化
this.foo = foo;
}
}
复制代码
另一种修改方式就是,将第一种情况,修改为第二种情况,即:
@Component
public class Bar {
private Foo foo;
@Autowired
public void setFoo(Foo foo) {
this.foo = foo;
}
}
复制代码
Spring 中设计了一个三级缓存用来解决前面介绍的循环依赖问题的处理。三级缓存包括:
beanName: () -> getEarlyBeanReference(beanName, mbd, bean)
的映射关系,value 是一个函数式接口 ObjectFactory。protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
复制代码
大概了解了 Spring 中的三级缓存后,我们再回过头来看一下 AbstractBeanFactory#getBean 过程。 它的实际工作是在 AbstractBeanFactory#doGetBean 中完成的。 doGetBean 方法的具体实现可以简化、抽象为:
protected T doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
// 缓存中存在
/**
* 如果 beanName 是一个 FactoryBean,则获取对应的 Bean
* 如果 beanName 是一个普通的 Bean,则返回这个 Bean 本身
*/
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 缓存中不存在
// 创建对象
if (mbd.isSingleton()) {
/**
* 这里的 getSingleton 是 getSingleton(beanName) 的重载版本
* 它接受一个 beanName 和 一个 ObjectFactory 作为参数
* 调用 ObjectFactory#getObject 产生一个实例
* 并通过 addSingleton(beanName, singletonObject); 将实例添加到 singletonObjects 中
* 这里 createBean 的代码就是前面提到的 Spring 创建 Bean 实例的过程 doCreateBean
*/
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
});
/**
* 同前面分支中的作用一样
*/
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
/**
* 判断 Bean 类型与 requiredType 类型是否一直,一致则直接返回,不一致则需要进行转换
*/
return adaptBeanInstance(name, beanInstance, requiredType);
}
复制代码
从上面的源码可以知道,当 DefaultSingletonBeanRegistry#getSingleton(beanName) 时,会先从多级缓存中取对象(可能是 bean instance,也可能是对应的 ObjectFactory)。 从多级缓存中取对象的源码如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
/**
* 判断第一级缓存中是否有完全可以可用的 Bean 实例,若有则返回;
* 若没有,则根据情况判断
* isSingletonCurrentlyInCreation(beanName) 检查的是在 `Set singletonsCurrentlyInCreation` 集合中是否包含要获取的 Bean 实例
* beanName 只在调用 beforeSingletonCreation(String beanName) 时被添加到 singletonsCurrentlyInCreation 集合中
* beforeSingletonCreation 在创建 bean,即 doCreateBean 之前调用,在创建过程完成以后,调用 afterSingletonCreation 从集合中移除 beanName
*/
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
/**
* 执行到这个分支,其实说明 Bean 已经在创建过程中了,只不过是尚未完全可用(即一级缓存中没有)
* 检查二级缓存,是否包含指定的 Bean
*
* 二级缓存里的内容何时被添加或设置进来的呢?
* 我们可以检查下 earlySingletonObjects.put 方法都在哪里调用。
* 检查后发现,其实 earlySingletonObjects 就是在当前方法中设置的,我们接着往下看。
*/
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
/**
* 这里的 allowEarlyReference 的意思就是指是否允许在二级缓存中创建一个对象,即是否允许暴露未完全可用的对象
* 如果 allowEarlyReference 为假,则不会操作二级、三级缓存,而仅检查一级缓存中是否有完全可用的 Bean 实例
* 这也意味着,不允许返回未完全可用状态的 Bean
*
* 当发现二级缓存中没有对象,同时又允许提前引用(即 allowEarlyReference 值为真)
* 则检查三级缓存中是否有对应的 ObjectFactory 对象,若有,则调用它的 getObject 方法产生对象,然后将其放置到二级缓存中,同时删除三级缓存中的对象工厂实例
* 若三级缓存中也没有对象工厂实例,则说面 bean 还未创建
*/
synchronized (this.singletonObjects) {
/**
* 这里会进行一个 double-check,避免多线程间的线程安全问题
*/
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
/**
* 三级缓存中存在对象工厂实例,则通过它产生一个 Bean 实例
* 加入到二级缓存中,同时删除三级缓存中的对象工厂实例
*/
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
复制代码
注:
有些读者看到这里可能会有个疑问,那二级缓存中的对象什么时候删除呢? 我们再来回头看下 doGetBean 中的代码片段:
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
});
复制代码
这里的 singleton
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
/**
* 检查 Bean 是否在创建过程中,避免重复创建,
* 不能解决的循环依赖也是在这里抛出异常
*/
beforeSingletonCreation(beanName);
boolean newSingleton = false;
try {
/**
* 这里调用的其实就是 AbstractAutowireCapableBeanFactory#createBean
* 然后会执行 doCreateBean(三个阶段)
*/
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
throw ex;
}
finally {
afterSingletonCreation(beanName);
}
if (newSingleton) {
/**
* 这里说明一个 bean 创建过程的三个阶段都执行完毕了
*/
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
/**
* 将 Bean 实例添加到第一级缓存
* 将第二级、第三级缓存中的对象删除
*/
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
复制代码
到这里为止,相信你对 Spring 创建 Bean 的过程以及处理循环依赖的机制能够有个大致的了解。 如果想了解的更深或跟多细节,可以自己单步调试下。 希望今天的内容能够对你有所帮助。