什么是循环依赖?
举个例子:
@Component
public class ObjectA {
@Autowired
private ObjectB b;
}
@Component
public class ObjectB {
@Autowired
private ObjectA a;
}
以上示例表示在 Spring 容器中定义了两个单例 bean:ObjectA 和 ObjectB,这两个 bean 相互依赖对方,都需要对方的实例注入到自己的属性中,这就属于循环依赖。同理,当 ObjectA 依赖 ObjectB 依赖 ObjectC … ObjectX 依赖 ObjectA 也属于循环依赖。
为什么要解决循环依赖
Spring 启动时,在 Bean 创建 的过程中,PostProcessor
执行 postProcessProperties()
时,会进行自动注入,自动注入时会去寻找依赖的 Bean,当 Bean 不存在或者还没有被创建时,会尝试创建 Bean,当被依赖的 Bean 中又依赖了正在创建的 Bean,正在创建中的 Bean 还没有完成初始化,由于是单例模式,无法再创建一个新的 Bean,所以可能导致 Bean 的创建无法继续下去
举个例子:
- ObjectA 依赖 ObjectB
- ObjectA 进行 Bean 创建
- ObjectA 在 Bean 创建的过程中发现依赖 ObjectB
- ObjectB 进行 Bean 创建
- ObjectB 在 Bean 创建 的过程中发现依赖 ObjectA
- ObjectA 正在创建中还未完成,需要注入依赖 ObjectB 才能创建完成,导致无法继续 Bean 的创建
Spring 是通过三级缓存的方式解决 Spring 的循环依赖问题
简单来说,当 Spring 在寻找依赖的过程中,先去缓存中查看依赖是否已经存在,如果已经存在就直接获取,如果不存在就创建依赖的 Bean
对应代码在:
DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference)
一级缓存(singletonObjects)
在 DefaultSingletonBeanRegistry
类中持有缓存:
// 一级缓存,保存的都是已经创建完成的 bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
这个缓存中保存的是所有已经创建完成的 Bean,Bean 中的依赖已经被正确解决
一级缓存不能解决循环依赖问题,但却是 Spring 中一个重要的缓存池,单例 Bean 在创建好之后,需要有一个地方缓存起来,避免重复创建,所以有了一级缓存(singletonObjects)
二级缓存(earlySingletonObjects)
在 DefaultSingletonBeanRegistry
类中持有缓存:
//二级缓存,保存的是未完成创建的bean
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
顾名思义,earlySingletonObjects
表示早期单例对象,这个缓存中保存的是未完成的 Bean,或者说是依赖未被解决的 Bean
二级缓存可以部分解决循环依赖问题,当被创建的 Bean 不需要 AOP 创建代理对象时,可以解决循环依赖问题
已上述 ObjectA 和 ObjectB 相互依赖举例:
- ObjectA 依赖 ObjectB
- ObjectA 进行 Bean 创建
- 在 ObjectA 实例化后(未完成依赖注入状态),将其加入二级缓存(earlySingletonObjects)
- ObjectA 在 Bean 创建的过程中发现依赖 ObjectB
- 先到一级缓存中查询 ObjectB,发现不存在
- 再到二级缓存中查询 ObjectB,发现也不存在
- ObjectB 进行 Bean 创建
- ObjectB 实例化后,将其加入二级缓存(earlySingletonObjects)
- ObjectB 在 Bean 创建 的过程中发现依赖 ObjectA
- 先到一级缓存中查询 ObjectA,发现不存在
- 再到二级缓存中查询 ObjectA,获取到 ObjectA(未完成依赖注入状态),注入到 ObjectB 中
- ObjectB 完成创建并返回
- 将 ObjectB 注入到 ObjectA 中
- ObjectA 和 ObjectB 都完成创建
此时 ObjectB 持有的 ObjectA 对象的引用不变,而 ObjectA 中已经完成了 ObjectB 的依赖注入,是创建完成的状态,所以循环依赖被解决
三级缓存(singletonFactories)
在 DefaultSingletonBeanRegistry
类中持有缓存:
// 三级缓存,保存的是 key: beanName, value: 创建bean的beanFactory
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
顾名思义,singletonFactories
表示单例 Bean 工厂对象,这个缓存中保存的是可以创建 Bean 的工厂对象
问题
在上述二级缓存解决循环依赖的过程中,没有考虑 AOP 导致的代理对象和原始对象不同的问题
举个例子:
启用 @EnableAspectJAutoProxy
注解
@Aspect
@Component
public class Azh3ngAspect {
@Before("execution(public void ObjectA.test())")
public void testBefore(JoinPoint joinPoint) {System.out.println("before")}
}
@Component
public class ObjectA {
@Autowired
private ObjectB b;
public void test() {}
}
@Component
public class ObjectB {
@Autowired
private ObjectA a;
}
ObjectA 中有一个切点,Spring 会生成 ObjectA 的原始对象和一个代理对象 ObjectAProxy,ObjectAProxy 中持有 ObjectA 的原始对象,所有依赖 ObjectA 的 Bean 中注入的都应该是 ObjectAProxy。在执行 test()
方法前,ObjectAProxy 中会先调用 testBefore()
方法,再调用 ObjectA 原始对象中的 test() 方法。
此时如果只有一级、二级缓存,创建过程如下:
- ObjectA 依赖 ObjectB
- ObjectA 进行 Bean 创建
- 在 ObjectA 实例化后(未完成依赖注入状态),将其加入二级缓存(earlySingletonObjects)
- ObjectA 在 Bean 创建的过程中发现依赖 ObjectB
- 先到一级缓存中查询 ObjectB,发现不存在
- 再到二级缓存中查询 ObjectB,发现也不存在
- ObjectB 进行 Bean 创建
- ObjectB 实例化后,将其加入二级缓存(earlySingletonObjects)
- ObjectB 在 Bean 创建 的过程中发现依赖 ObjectA
- 先到一级缓存中查询 ObjectA,发现不存在
- 再到二级缓存中查询 ObjectA,获取到 ObjectA(未完成依赖注入状态),注入到 ObjectB 中
- ObjectB 完成创建并返回
- 将 ObjectB 注入到 ObjectA 中
- ObjectA 在完成依赖注入后,创建代理对象 ObjectAProxy
- 将 ObjectAProxy 返回加入一级缓存
singletonObjects
中,移除二级缓存中的 ObjectA 原始对象
此时 Spring 单例池中持有的 ObjectA 是 ObjectAProxy,而 ObjectB 持有的 ObjectA 对象是原始 ObjectA,出现错误
解决
Spring 通过加入三级缓存解决这个问题:
- ObjectA 创建 Bean
- 将 ObjectA 的 beanName、BeanDefinition、原始 bean,传入并生成一个
ObjectFactory
,将其加入三级缓存(singletonFactories) - ObjectA 在 Bean 创建的过程中发现依赖 ObjectB
- 在一级缓存、二级缓存、三级缓存中查询 ObjectB,均不存在
- ObjectB 进行 Bean 创建
- ObjectB 实例化后,将其加入二级缓存(earlySingletonObjects)
- ObjectB 在 Bean 创建 的过程中发现依赖 ObjectA
- 在一级缓存、二级缓存中查询 ObjectA,发现不存在
- 在三级缓存中查询 ObjectA,获取到
ObjectFactory
- 执行
ObjectFactory
的getBean()
方法,判断 ObjectA 是否需要进行 AOP 创建代理对象 - ObjectA 需要 AOP,创建代理对象 ObjectAProxy
- 将 ObjectAProxy 加入二级缓存,移除三级缓存中的
ObjectFactory
- 将 ObjectAProxy 注入 ObjectB 中
- ObjectB 完成创建
- 将 ObjectB 注入到 ObjectA 中
- ObjectA 完成创建
- ObjectA 执行初始化后步骤:判断 ObjectA 是否需要 AOP,以及是否已经创建代理对象
- ObjectA 已经创建了代理对象 ObjectAProxy,返回
- 将 ObjectAProxy 返回加入一级缓存
singletonObjects
中,移除二级缓存中的 ObjectA 原始对象
扩展问题
问:原形 bean 的循环依赖是否能解决?
答:由上述流程可以推断,当原形 bean 在创建过程中出现循环依赖,如果整个依赖链路中存在一个单例 bean,则此单例 bean 可以被缓存,从而完成 bean 的创建,使整个依赖链路正常创建返回;如果整个链路中所有的 bean 都是原形 bean,则无法解决循环依赖。
问: 为什么 @Async
注解会导致 Spring 解决循环依赖失效?而 @Transactional
注解不会?
答: 因为 @Async
注解会向 Spring 中 加入一个新的 BeanPostProcessor(AsyncAnnotationBeanPostProcessor
),会对原始对象生成新的代理对象,与 AOP 的代理对象不同,导致 Spring 在创建过程中会出现两个不同的代理对象,导致错误。@Async
的代理对象不是在 getEarlyBeanReference()
中创建的,是在 postProcessAfterInitialization
创建的代理,而 @Transactional
使用的是 BeanFactoryTransactionAttributeSourceAdvisor
,而不是 BeanPostProcessor,也就不会在 Bean 创建的过程中创建新的代理对象。
问: 上述例证中都是使用字段注入,Set 方法注入和构造方法注入导致的循环依赖能解决吗?
答: Set 方法注入和字段注入类似,是在 Bean 创建之后再解决依赖问题,所以 Bean 可以被缓存,所以可以解决循环依赖。但是构造方法注入导致的循环依赖无法被解决,因为 Bean1 在创建前,发现需要依赖 Bean2,于是尝试获取依赖 Bean2,此时 Bean1 还没有被创建出来,无法被缓存,当 Bean2 也依赖了 Bean1,而 Bean1 没有缓存,导致两个 Bean 都无法被正确创建,也就无法解决循环依赖问题,最终会抛出 BeanCurrentlyInCreationException
异常。
问: 当出现 Spring 无法解决的循环依赖时,如何解决?
答: 可以通过 @Lazy
注解解决构造方法、@Async
等导致的循环依赖,因为 @Lazy
注解的 Bean 无需立刻创建,可以在需要时再创建,也即构造方法的调用不会被卡住,代理对象实例化的延后也可以避免注入错误的对象。