Spring 循环依赖【2】Spring是如何解决的?

243 阅读8分钟

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

Spring代码

上面已经解释了:

  • 为什么会出现循环依赖
  • 以及如何解决循环依赖

上述总结

到现在算是分析完解决循环依赖问题了,流程如下:

A的:

  • beanNameAsingletonsCurrentlyInCreation这个容器里。【0】

  • 实例化A,得到一个对象,并把这个还没填充属性的A放到缓存A(singletonFactories)中。【1】

  • 填充A中的B属性(根据B属性类型,名称去查找),去单例池中获取B(getSingleton()),获取不到就取有没有生成过提前暴露的,找不到就去缓存A中取,还获取不到就去创建B。【2】

接下来进入到B的生命周期:

  • beanNameBsingletonsCurrentlyInCreation这个容器里。
  • 实例化B,得到一个对象,并把这个还没填充属性的B放到缓存A中
  • 填充B中的A属性(根据A属性类型,名称去查找),首先去单例池中获取A(getSingleton()),获取不到单例A就去提前暴露bean(earlySingletonObjects)中取,获取不到就取缓存A(singletonFactories)中的并生成,此时能获取到了,就赋值,并放到earlySingletonObjects中。【4】
  • 填充其他属性,初始化前BP,初始化,初始化后,放入单例池。

随后回到A的生命周期:

  • 填充其他属性,初始化前BP,初始化,初始化后【6】,放入单例池。

所以回过头来看,为什么Spring的循环依赖需要这样解决?原因如下:

  • Spring需要支持单例,因此需要第一级缓存singletonObjects

  • Spring需要支持多线程下bean在填充完毕之前不能提前暴露到单例池中,因此需要第二级缓存earlySingletonObjects

  • Spring需要支持代理,因此需要第三级缓存singletonFactories

这里的三个缓存:

  • singletonObjects(单例池,这里放的是完全实例化且初始化好的bean)

  • earlySingletonObjects(这里放的是提前实例化的bean,这里并没有经过完整的生命周期,存在意义:提前把这个bean存起来,就是用来解决多个循环依赖的时候,保证bean的单例性质

  • singletonFactories(打破循环的关键,只有在这里存了,才能保证二级缓存的内容可以正确生成(AOP相关))

  • 如果这里使用二级缓存,那么就把earlySingletonObjectssingletonFactories混合在一起即可,即:在【1】中我们把实例化的对象就放到这里,这样做也是解决了循环依赖,但这样子就不能保证如果需要代理对象时,代理对象的单例性质(没有地方去保存)。

工厂方法生成 - 对应【1】

//其实在进入【1】之前,需要判断:
//1.是否单例?2.是否允许循环依赖?3.是否单例在创建当中
//如果不支持循环依赖就会往下走,就不会往第三级缓存取,出现了循环依赖就会报错
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");
   }
    //这里就对应上面的【1】
   addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

//看,这里做的事情,就是:往第三级缓存里存,并从二级缓存里把对应的bean清除
	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);
			}
		}
	}

而这里的getEarlyBeanReference实际上是BP的通知式循环调用:

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;
}

默认的实现实际上只有AbstractAutoProxyCreator:

public Object getEarlyBeanReference(Object bean, String beanName) {
   Object cacheKey = getCacheKey(bean.getClass(), beanName);
   this.earlyProxyReferences.put(cacheKey, bean);
   return wrapIfNecessary(bean, beanName, cacheKey);
}

B尝试获取A实例填充 - 对应【4】

在填充属性的时候,就回去调用getBean方法(在前面的依赖注入部分可以trace):

protected <T> T doGetBean(
      String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
      throws BeansException {

   String beanName = transformedBeanName(name);
   Object beanInstance;

   // Eagerly check singleton cache for manually registered singletons.
    //这里
   Object sharedInstance = getSingleton(beanName);
   
    //.......
}


	@Nullable
//这里的逻辑就是【4】中Spring的实现
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		//先查看单例池中有没有
		Object singletonObject = this.singletonObjects.get(beanName);
        //如果现在不在创建中就跳出取创建了;如果并不是,那么就进入【4】中的逻辑了
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            //这里就从二级缓存里取了,如果这里获取到了,那么依然会跳出
			singletonObject = this.earlySingletonObjects.get(beanName);
			if (singletonObject == null && allowEarlyReference) {
                //接下去就和【4】的描述一摸一样了:单例池->二级->三级
				synchronized (this.singletonObjects) {
					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;
	}
  • 这里有一个问题:为什么要加锁?
//接下去就和【4】的描述一摸一样了:单例池->二级->三级
				synchronized (this.singletonObjects) {}

为什么要加锁?

因为这一步,需要保证原子性:

if (singletonFactory != null) {
   singletonObject = singletonFactory.getObject();
   this.earlySingletonObjects.put(beanName, singletonObject);
   this.singletonFactories.remove(beanName);
}

这里需要让这两级缓存中,同一个beanName只能从这两级缓存中获取一个对象

如果前面已经执行AOP代理了,那么这里【6】如何处理?

上述的代码看起来已经解决一大半问题了,但是这里我们需要注意:

  • 无论是AOP生成的代理对象,还是原始对象,在单例模式下我们都只需要一个bean。

在【1】和【4】中我们知道了是在AbstractAutoProxyCreator中进行了提前的AOP。

在步骤【6】中,我们会执行下面这个回调:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

这里就是通过earlyProxyReferences判断是否在【4】进行了AOP。

  • 如果前面进行了AOP,那么这里就会把原始的bean返回。

那么这里就有个问题:如何获取到代理对象,放到单例池的缓存中?

  • 我们可以从二级缓存中,来获取代理对象。

在bean的生命周期中最后的部分,会来进行这部分的判断:

//abstractAutowireCaplableBF
//这个变量在上面经过提前AOP之后,就是true了
if (earlySingletonExposure) {
    //看这个变量名称,说明这里就是从二级缓存中取得
   Object earlySingletonReference = getSingleton(beanName, false);
   if (earlySingletonReference != null) {
       //这里就是看看:前面暴露得对象,是否是原始bean?如果是的话,让暴露的对象变成AOP代理对象,接下去代理对象和原始对象不是同一个对象的问题就解决了,我们使用代理对象来完成后面的生命周期
      if (exposedObject == bean) {
         exposedObject = earlySingletonReference;
      }
       //进入到这里,不一样的情况就出现了:
       //1.出现原因:除了AOP代理了,还有其他的BP也对bean代理了【7】
       //细看这里的代码,其实只是对进行处理并抛出的,相当于Spring认为走到这里,就已经是异常事件了【8】
      else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
         String[] dependentBeans = getDependentBeans(beanName);
         Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
         for (String dependentBean : dependentBeans) {
            if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
               actualDependentBeans.add(dependentBean);
            }
         }
         if (!actualDependentBeans.isEmpty()) {
            throw new BeanCurrentlyInCreationException(beanName,
                  "Bean with name '" + beanName + "' has been injected into other beans [" +
                  StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                  "] in its raw version as part of a circular reference, but has eventually been " +
                  "wrapped. This means that said other beans do not use the final version of the " +
                  "bean. This is often the result of over-eager type matching - consider using " +
                  "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
         }
      }
   }
}

【7】:exposedObject在这里最后被改变了:

exposedObject = initializeBean(beanName, exposedObject, mbd);

这个方法会调用

applyBeanPostProcessorsBeforeInitialization

这里的BP,也可以对bean做代理的包装,比如:@Async注解会往BP的list里加入

AsyncAnnotationBP

这个BP在AOP的BP之后,因此【7】的地方就有可能在这里被再一次代理,导致bean和exposedObject不一致,再往下就可能会抛异常。

但是如果是@Transactional注解,则不会报错:

  • 原因是:@Transactional注解工作方式和@Async不同,并不是通过添加BP来实现代理的,添加的是

TransactionManagementConfigurationSelector,因此在这里不会触发。这部分和AOP相关,后续再看看AOP。

【8】:抛异常的原因:因为这里如果发生了再次代理,那么放到单例池中的对象A,和注入给别的对象AA,这两个对象不一致,同样地违反了单例规则。

如果这里确实需要多次代理,一个解决方案是:

  • 对于代理的对象,加一个@Lazy注解。

这样子在生命周期中,A注入的B对象就不会走B的生命周期,而是赋一个被Lazy相关BP生成的代理对象的值。

这样子调用B的时候,才会去实际获取B(获取或者创建)。

同时,如果是B上有代理,A上没有代理,也不会出问题,原因如下:

  • 因为我们这里是:
    • A先进入生命周期
    • 在A注入B的过程中完成了B的生命周期,在这个步骤中发生了提前AOP。
    • B生命周期完成了,A完成后续一系列步骤,最后放入单例池之前发生了错误。

那么,我们如果让B不仅是AOP也是Async,这里其实就没问题:

  • 因为B的AOP和Async,都在初始化后的BP回调中执行的

那么初始化后回调的结果的代理对象,只会是同一个,这里就不会报错了:其实也就是说,这里的earlySingletonExposure就会是false,就不会报错了。

如果bean是原型的呢?

如果AB都是圆形的,Spring是否能解决循环依赖?

很遗憾的是,Spring中对于原型bean,并不能解决原型中循环依赖的问题。

因为:

  • 每一次bean的创建,属性都需要是新的。

因此,上面说的解决方案,只针对单例,而非原型。

但是,如果循环依赖的依赖链上,有一个是单例的,就可以解决。

循环依赖的其他情况

构造方法的循环依赖

不能解决。因为这种情况下,对象都无法创建,也就是说A生命周期的实例都无法生成,上面的解决方案都没办法往下进行。

一个折衷的解决方案是:

  • 在构造方法上,加上@Lazy注解。

自己注入自己

A中注入了A,按照定义,这也算是一种循环依赖。

流程是相同的,步骤也是相同的:因为有第三级缓存。