一、问题
本文我们讨论下常见的八股文,Spring如何解决循环依赖。其实我们之前的两篇文章已经解释过了,本文进一步详细探讨一下。
🤔什么是循环依赖?
比如我们有两个类A/B,A对B有依赖,也就是A有个属性为B,而B对A也有依赖,这就是循环依赖。也可以更复杂,比如A -> B -> C -> A这种三个类的,当然也可以更多类形成这种“套圈”的依赖,都属于循环依赖。
🤔有什么问题
回顾一下Spring Bean的创建流程,大概为:
- 实例化(createBeanInstance)
- 依赖注入(populateBean)
- 初始化(initializeBean)
- 注册销毁方法(registerDisposableBeanIfNecessary)
如果Spring没有解决循环依赖,那么假设A/B循环依赖后:
- A实例化完成后,进行依赖注入,从BeanFactory中查找B,触发B的创建
- B实例化,进行依赖注入,从BeanFactory中查找A,触发A的创建
- 触发B创建,实例化。。
- 触发A创建,实例化。。
- 。。。
完犊子了,死循环了。
🤔如何解决?
如果是手动编码,该如何解决呢,以最简单的A/B互相依赖为例,我们可以这样解决。
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
Spring的解决方案也类似,实例化A对象后,先把这个还没有创建完成的“半成品”(也就是还没有执行属性注入,初始化等后续流程)放到map中缓存起来。这样,创建B之后,在B注入属性时,就可以先把这个“半成品”,注入到B中,也就破除了死循环。不过因为Spring的动态代理问题,实际实现上,多使用了一个map。
二、源码
在阅读源码前,我们先梳理一下骨架。Spring使用了三个map管理单例的bean,也就是著名的三级缓存。分别是
(出自DefaultSingletonBeanRegistry) :
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
- singletonObjects,存放完整单实例Bean的map,也就是所谓的“一级缓存”。
- singletonFactories,半成品的单实例对象工厂的map,也就是所谓的“三级缓存”。
- earlySingletonObjects,提前暴露的半成品单实例bean,这里的提前(early),就是因为循环依赖而提前暴露的bean,会从singletonFactories获取工厂,调用工厂方法获取bean,再放到这个map中。这个map,就是所谓的“二级缓存”。
🤔什么是半成品bean?
还没有完成属性注入和初始化等创建流程的bean,详细的bean创建流程,可以看第一章。
假设A/B出现了循环依赖,并且A先初始化,那么在Spring中,创建的流程为:
- A实例化,实例化后,使用半程品的A对象,构造工厂,放入工厂map(singletonFactories)中。
- A依赖注入,触发B的创建。
- B实例化,使用半程品的B对象,构造工厂,放入工厂map(singletonFactories)中。
- B依赖注入,触发A的创建。A创建前,会去工厂map检查,是否存在
- 存在,获取工厂,执行工厂方法,获取提前暴露的bean。
- 工厂方法执行过程中,会调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法,比如AOP,会在这里进行提前代理。
- 把提前暴露的bean,放入到提前暴露的map(earlySingletonObjects)中。
- 获取到A的提前暴露的对象,注入B中。
- B执行后续的初始化等流程,完成完整的bean创建流程后,把完整对象放到单实例map(singletonObjects)中,并在另外两个map中移除对象。
- A成功获取到B,进行依赖注入以及后续初始化流程。
- 创建完成后,把完整对象放到单实例map(singletonObjects)中,并在另外两个map中移除对象。
以上就是Spring解决循环依赖的大致流程,下面我们看下源码。
2.1 缓存半成品工厂
在Bean创建完成后,会有这样一段代码(出自AbstractAutowireCapableBeanFactory#doCreateBean):
// 单例 && 允许循环依赖 && 该单例正在创建,则使用该单例,构造一个ObjectFactory<?>,放入工厂map中。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
addSingletonFactory源码,也很简单,不再赘述。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
相对的,在bean注入属性,初始化完成后,会把该半成品移除掉,同时把完整的bean放入的singletonObjects中。(出自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);
this.registeredSingletons.add(beanName);
}
}
调用链为:getBean -> doGetBean -> getSingleton -> addSingleton。
2.2 发现循环依赖
注入属性时,会通过BeanFactory的getBean方法,获取依赖,如果是单例,会尝试先获取单例缓存。最终调用getSingleton方法(出自DefaultSingletonBeanRegistry)。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 尝试在完整的单例map中获取bean,成功直接返回
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//没有完整bean获取到,并且该bean正在创建,则尝试从提前暴露的半成品map中获取bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//没有获取到提前暴露的半成品,则从工厂map中获取工厂
synchronized (this.singletonObjects) {
// 经典的并发double check,尽量减少加锁可能
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) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
梳理一下:
- 从三个map中依次查找,比较简单。
- 经典的double check方式加锁,尽量减少加锁可能。
2.3 触发getEarlyBeanReference
如果从工厂map中获取到了工厂,则调用工厂方法获取对象,这个工厂方法,即上一步中放进来的那个lamba表达式:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
也就是调用了这个方法,执行SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
这里就解释了,为什么需要三个map了,需要在存在循环依赖的场景下,bean被提前暴露时,提供一个扩展能力。也就是,在此种场景下,会执行这个SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法。
那么这个扩展能力谁实现了呢,对,就是Spring AOP功能(在上一篇文章中介绍过)。代码出自AbstractAutoProxyCreator。
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
简单来说,就是如果发生了循环依赖,需要提前暴露,那么AOP,需要提前做动态代理。否则注入到其他bean的,就是一个未代理对象。
三、扩展
3.1 构造器构造
3.1.1 都是构造器依赖
之前,都采用属性方式注入,使用上述方式解决循环依赖。那么假如是构造器方式依赖呢,也就是这样:
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
这种似乎无解,手动构造都无法成功。额,其实还真有解,使用@Lazy,像这样,注意一定是放在构造方法的参数里,而不是放在类上。
@Component
public class A {
private B b;
public A(@Lazy B b) {
this.b = b;
}
}
在这种情况下,构造A的时候,并没有真正实例化B,而是使用代理类包装起来,用到的时候,才会实例化。也就是破坏了循环依赖,构造时只有B依赖A,而A没有真正的依赖B。
3.1.2 一个构造器,一个非构造器
还有种场景,一个是构造器,另一个不是:
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B {
@Resource
private A a;
}
在不指定两者的构造顺序情况下,可能成功,也可能失败。既然不指定,那么Spring并没有保证,哪个bean会先初始化。
如果是先A,后B,那么:
- 实例化A,仅有一个构造器,那么使用该构造器,寻找依赖,也就是B,触发B的创建。
- 实例化B
- 实例化完成后,构造工厂,放到工厂map中。
- 依赖注入,触发创建A,然而,A还没有实例化完成。。。
这时,你会收到一个异常,总之,就是发现了循环依赖,无法解决。
Exception encountered during context initialization - cancelling refresh attempt:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in class path resource [com/hellohu/spring/bean/annotation/A.class]: Unsatisfied dependency expressed through constructor parameter 0;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
反之,如果先B后A,那么:
- 实例化B,实例化后包装成工厂,放入工厂map中。
- 依赖注入,寻找依赖,也就是A,触发A的创建。
- 实例化A
- 仅有一个构造器,那么使用该构造器,寻找依赖,也就是B,触发B的创建。
- 从工厂map中获取工厂,提前暴露。。。
- 仅有一个构造器,那么使用该构造器,寻找依赖,也就是B,触发B的创建。
不再向下推演,你也可以发现,循环依赖解决了。由于Spring并不会保证初始化顺序,你极有可能本来运行好好的,突然有一天,你增改了某些无关的代码,就突然完犊子了。
🤔该怎么解决呢?
很简单
- 不采用构造器的方式依赖,使用属性或者setter方法依赖。
- 固定创建顺序,比如使用@DependsOn注解,指定A依赖B,以达到先初始化A的目的。
- 也可以使用@Lazy,破坏循环依赖
使用@DependsOn
@Component
@DependsOn("b")
public class A {
...
}
使用@Lazy
@Component
public class A {
public A(@Lazy B b) {
this.b = b;
}
}
3.2 多例
之前,都是单例的场景,其实从源码上你也能看出来,所谓的三级缓存,都是存储单例对象(非常合理,多例也不能缓存啊)。
3.2.1 多例互相依赖
如果A/B都是多例的呢,也就是这样:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class A {
@Resource
private B b;
}
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class B {
@Resource
private A a;
}
很显然,正常情况下,Spring是没有办法解决这种循环依赖的。因为多例就不会进入缓存,也就没有办法解决。但也不是完全没办法,可以使用@Lazy,注意是加到属性上。
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class A {
@Resource
@Lazy
private B b;
}
本质上还是使用@Lazy破坏循环依赖。不过这里还是有问题,我们为A/B增加空的构造方法,创建时打印一下。类似这样
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class A {
@Resource
@Lazy
private B b;
public A() {
System.out.println("a create");
}
public void useB(){
System.out.println(b.toString());
}
}
@Component
public class B {
private A a;
public B(A a) {
this.a = a;
System.out.println("b create");
}
}
测试代码:
try (ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("spring/beansAutowire.xml");) {
A a= (A) ctx.getBean("a");
a.useB();
}
输出:
a create
b create
a create
发现了问题了吧,A->B->A,这里的A是两个对象,仔细想想,也没啥问题,但又好像有问题。解决了,又好像没解决(看似说了什么,又好像啥也没说)。
如果真想创建两个互相依赖的多实例Bean,直接手动创建吧,像这样:
try (ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("spring/beansAutowire.xml");) {
A a= (A) ctx.getBean("a");
B a= (B) ctx.getBean("a");
a.setB(b);
b.setA(a);
}
3.2.1 单例多例互相依赖
如果是一个单例,一个多例呢?
@Component
public class A {
@Resource
private B b;
public A() {
System.out.println("a create");
}
}
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class B {
@Resource
private A a;
public B() {
System.out.println("b create");
}
}
这种创建不会有问题,因为ApplicationContext在初始化的时候,会把所有的单实例bean都初始化。也就是说,一定是先创建单例,也就是会进入所谓的“缓存”,也就可以解决循环依赖。但这里还是有一个问题,是个哲学问题。
单实例Bean为啥用引用多实例的Bean呢,单实例仅创建一次,那么注入的B也仅创建一次,也就是说,B在A中,某种意义上也是单实例的,是不是哪里不对劲。
此时,可以使用@Lookup注解,这里不做介绍了,Spring 常用注解之@Lookup