当两个bean互相依赖,或者多个bean相互依赖并形成一个环状结构的时候,就形成了循环依赖的问题。例如下图:
关于bean的实例化过程,我在spring源码学习_bean的实例化过程中有详细讲解,这里直接说结论。
① bean在实例化时,spring首先会找到其指定的构造方法,生成这个bean的java对象。此时bean只是单纯的java对象,其中的依赖并没有被注入。这个阶段的bean我们可以理解为是半成品的bena或是早产的bean。
② 对象生成后,spring就会在spring容器中获取其依赖的对象,并将依赖对象赋值给属性。如果该依赖在spring容器中不存在,则先实例化这个依赖。
我们以上图的第一个情况为例。spring先实例化A,生成了A类的对象,在第二步注入属性时,发现spring容器中并没有B,则转去实例化B。B的对象创建后,又会尝试在Spring容器中寻找A,结果当然是找不到的。因为A现在还是半成品,并不是真正的bean,也没有被放入spring容器中。所以这时spring又得去实例化A。这样就形成了一个死循环,导致这两个bean永远都没法实例化。
尽管我们在实际开发中会避免设计出如此糟糕的依赖关系,但是spring依然为帮我们解决了这个问题。下面,我们就通过源码看看spring是如何解决的。
这里只贴出doGetBean方法中与循环依赖相关的代码
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
Object bean;
//这里尝试直接从缓存中获取bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else{
//缓存中没有,则创建这个bean
if (mbd.isSingleton()) {
//这里通过lambda表达式,传入了一个对象工厂
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {....}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
return (T) bean;
}
源码中出现了两个getSingleton方法,第一个getSingleton的功能是从缓存中获取bean。如果这个bean是第一次被实例化,那就一定获取不到。第二个 的功能在于创建这个bean。所以这段代码整体的逻辑就是,如果缓存中存在这个bean,则直接返回,如果没有,则创建一个新的实例。
这里咋一看,好像和循环依赖并没有太大的关系,但是深入看看第一个getSingleton方法的实现,发现其并不是从spring容器中取对象这么简单。
public Object getSingleton(String beanName) {
//调用另一个重载的getSingleton方法
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先尝试从spring容器中获取bean实例
Object singletonObject = this.singletonObjects.get(beanName);
//如果容器中没有,且这个单例bean处于正在创建状态
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从一个早产的单例对象池中获取bean
singletonObject = this.earlySingletonObjects.get(beanName);
//如果依然没有,且允许被提前引用的话
if (singletonObject == null && allowEarlyReference) {
//从单例工厂池中获取到一个对象工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//通过对象工厂构建单例对象
singletonObject = singletonFactory.getObject();
//将构建出的对象,放入earlySingletonObjects中
//显然earlySingletonObjects是一个缓存,下次可以就直接冲这个容器中拿到对象,而不需要工厂再次构建
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
重点看第二个重载方法的实现。spring显然不只是单纯的从单例池中拿对象。在spring容器中没有这个bean存在,且isSingletonCurrentlyInCreation判断为true时,spring容器会尝试从singletonFactories获取一个工厂并通过这个工厂构建出一个对象。
那么问题就来了
- 何时这个bean被标记成了正在创建状态
- singletonFactories这个个容器中装的是什么,是何时注入内容的。
- singletonFactories中获取的工厂构建出的对象是什么。
顺着这个线索,我们继续看第二个 getSingleton 方法。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
//避免多线程问题,又一次尝试从单例池中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//创建的前置处理,将这个bean标记为正在创建状态
beforeSingletonCreation(beanName);
try {
//调用传入工厂的getObject方法,创建对象
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {......}
catch (BeanCreationException ex) {......}
finally {......}
}
return singletonObject;
}
}
protected void beforeSingletonCreation(String beanName) {
//将bean标记为正在创建状态
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
这个getSingleton方法主要功能就是调用传入的单例工厂中的getObject方法去创建这个bean的实例。并在创建实例之前,把这个bean标记为正在创建状态(装入singletonsCurrentlyInCreation容器中)。
所以当一个bean第一次被创建时,他并不是正在创建状态。进而会进入到第二个getSingleton方法中,创建这个bean的新实例,并被标记为正在创建状态。
上面提到的第一个问题已经解决,现在进入到bena的创建过程,寻找后两个问题的答案。
来到 doCreateBean方法,这个依然只贴出与循环依赖相关的代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
//创建这个bena的实例
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
//获取到bena的实例,这里的实例是半成品的bean,还没有被注入过属性
final Object bean = instanceWrapper.getWrappedInstance();
//bean是单例的,且是正在创建的,且allowCircularReferences为true
//前两点确定为true了,allowCircularReferences是在本类中初始值为true的一个属性
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
//这里就向singletonFactories中添加了属性
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
//装配bean,这里会自动注入bean的所有依赖
//若依赖在spring容器中不存在,则先创建这个依赖实例
populateBean(beanName, mbd, instanceWrapper);
}
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//再次确认了单例池中没有这个bean
if (!this.singletonObjects.containsKey(beanName)) {
//向singletonFactories中注册了传入的工厂
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
可以看到,单例bena在实例化成为半成品后,向singletonFactories中注册了一个对象工厂,并通过lambda表达式,将工厂中getObject方法的实现指向到了getEarlyBeanReference方法。
现在来重新回顾一下A和B两个类互相依赖时,这两个类的实例化过程。
当A第一次被创建时,他并不是正在创建状态。进而会进入到第二个getSingleton方法中,创建这个bean的新实例,并被标记为正在创建状态。 A的实例创建完毕后,向singletonFactories中注册了一个对象工厂,然后尝试自动注入B。此时的B实例在spring容器中并不存在,则调用getBean方法获取B的实例。
由于B也是第一次被创建,则相同的步骤进到第二个getSingleton方法中。被标记为正在创建状态后,创建了B的实例,向singletonFactories中注册一个对象工厂,然后B又开始尝试自动注入A对象。A此时还没完成属性的注入,所以spring容器中找不到。就再次调用getBean方法获取A的实例。
这次则是A的第二次创建过程,他已经被标记为正在创建状态,且在singletonFactories中有他注册的对象工厂。所以在第一个getSingleton方法中,就能获取到A注册的对象工厂,进而获取到其生产的对象。并将这个对象作为A的实例返回。
B获取到A的实例后,B的装配过程顺利完成。进行初始化之后,B成为一个真正的bean。此时A也就获取到了B的实例,进而A的实例化也完成了。
到了这里,循环依赖的死循环问题就已经解决了。但还有个问题没解决,getEarlyBeanReference方法给我们返回的是一个什么样的对象,这个对象为何可以代替真实的bean。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
//注意,这里传入的bean是半成品bean
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//掉用getEarlyBeanReference方法获取
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
//直接就把半成品bean返回了
return bean;
}
到了这里就是真破案了。在第二次获取A的实例时,第一个getSingleton方法返回的就是A的半成品对象。
由于在所有bean都是单例的情况下,A中依赖的B对象和B本身是同一个对象,B中依赖的A对象和A本身也是同一个对象。
把A的半成品注入给B,B完成了实例化过程。此时A所依赖的B就是这个实例化完成的B,A也就完成了实例化。有点绕。
换一种情况,如果我们在原型bean的情况下,每次调用getBean方法返回的bean都应该是一个全新的bean。第二次获取A时,就没办法将第一次的半成品返回,这个死循环也就将一直进行下去。
还有一种情况,当我们不是属性注入,而是构造器注入时。我们在对象的创建阶段就会被卡住,就更不谈半成品bean的返回了,所以构造器注入的循环依赖也是无法解决的。
最后是spring解决循环依赖的流程图