Spring中循环依赖
1. 什么是循环依赖
两个类之间相互引用,或者多个类之间依次引用对方,最终构成一个环,例如下图所示,ServiceA类中定义了一个ServiceB类型的属性,ServiceB类中定义了一个ServiceC类型的属性,ServiceC类中定义了一个ServiceA类型的属性,三个类型之间引用构成了一个环。
2. 循环依赖如何解决
2.1 spring容器中bean的创建流程
在描述循环依赖如何解决之前,下面用一张图来回忆下spring容器中bean的创建流程。spring中BeanFacgtory根据getBean方法获取bean,下图描述了bean创建的几个重要步骤,其中和循环依赖相关的部分用绿色标记。
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
2.2 处理循环依赖
了解了上面bean创建的流程之后,再去理解循环依赖的处理逻辑会更容易些。Spring在singleton scope下解决了setter注入方式的循环依赖。具体流程可以见下图所示:
这其中主要用到三个Map,描述如图
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
下面描述下上图中比较重要的几个地方。
2.2.1 提前暴露
在bean完成实例化步骤之后,spring容器判断是否需要提前暴露刚完成实例化的bean,如果需要则创建一个ObjectFactory放到singletonFactories中。
- (1) 创建bean中关于是否需要提前暴露的判断逻辑
// 判断是否需要提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
- (2) addSingletonFactory 方法
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);
// earlySingletonObjects移除
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
2.2.2 从缓存中取
后续循环引用的时候从缓存中获取提前暴露的bean
2.2.3 循环依赖校验
(1) 在bean创建完并初始化之后,会校验提前暴露出去的bean对象和最终生成的bean对象是否相同,如果不同,则会继续查看是否有其他已经被创建过至少一次的bean依赖了该bean,如果有则会抛出异常,认为被依赖的bean对象注入的该bean对象不是最终版本的对象。
(2) 该校验主要是用在bean被AOP增强的场景中。我们知道AOP增强是在初始化流程中进行的,被AOP增强过的bean和初始阶段被实例化的bean已经不是同一个对象了。如果被依赖的bean注入了初始阶段实例化的bean就会出现问题。
(3) 校验具体实现:
- 前置依赖,主要使用到一个map -
dependentBeanMap
和一个set -alreadyCreated
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry
/** Map between dependent bean names: bean name --> Set of dependent bean names */
private final Map<String /*依赖bean name*/, Set<String> /*被依赖bean集合(依赖了name为该key的bean集合)*/> dependentBeanMap = new ConcurrentHashMap(64);
org.springframework.beans.factory.support.AbstractBeanFactory
/** Names of beans that have already been created at least once */
private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256));
-
添加阶段
-
dependentBeanMap - 在populateBean方法中注入依赖属性之后,会调用
org.springframework.beans.factory.config.ConfigurableBeanFactory#registerDependentBean(String beanName, String dependentBeanName)
方法将两个bean之间的依赖关系添加到dependentBeanMap
中。-
- 如果是通过@Resource注解修饰的属性,调用逻辑在类
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
里面
- 如果是通过@Resource注解修饰的属性,调用逻辑在类
-
- 如果是通过@Autowired注解修饰的属性,调用逻辑在类
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
里面
- 如果是通过@Autowired注解修饰的属性,调用逻辑在类
-
-
alreadyCreated - 在
doGetBean
方法中尝试从缓存中获取之后实例化之前会将要创建的bean name添加到alreadyCreated
中
-
protected void markBeanAsCreated(String beanName) {
if (!this.alreadyCreated.contains(beanName)) {
synchronized (this.mergedBeanDefinitions) {
if (!this.alreadyCreated.contains(beanName)) {
// Let the bean definition get re-merged now that we're actually creating
// the bean... just in case some of its metadata changed in the meantime.
clearMergedBeanDefinition(beanName);
this.alreadyCreated.add(beanName);
}
}
}
}
- 具体实现逻辑
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
------------------------------------------------------------------------------
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
// 只有被循环依赖过该返回才不为null
if (earlySingletonReference != null) {
// 没有被AOP增强或者被AOP增强的逻辑也在实现了接口org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法中被实现过
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 存在依赖当前bean的其他bean
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 判断被依赖的bean是否已被创建好
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 存在其他bean依赖了该bean的初始版本
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 " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
上面代码逻辑如下图所示:
(4) 通常AOP增强(比如@Aspect注解)是通过org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator
实现的。该类实现了SmartInstantiationAwareBeanPostProcessor
接口重写了其中的getEarlyBeanReference
方法,所以提前暴露出去的bean已经是被增强过的实例了。
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
return wrapIfNecessary(bean, beanName, cacheKey);
}
但是@Async注解的实现类没有实现SmartInstantiationAwareBeanPostProcessor
接口,也就没有重写getEarlyBeanReference
方法,所以被@Async注解修饰的类(或者其中有被@Async注解修饰的方法)如果出现循环依赖引用可能会导致循环依赖检验不过最终导致服务启动失败。
(5) 因为存在AOP增强的功能,所以这也是为什么Spring中处理循环依赖需要使用三个map的原因。
3. 不能解决的场景
-
(1) prototype作用域的bean
-
(2) 构造器注入方式
-
(3) 针对某些AOP场景中,spring容器提前暴露生成的bean和最终构建的bean不相同
4. 总结
- Spring容器中解决了singleton作用域下的setter注入方式的循环依赖,是因为在bean实例化之后在属性被注入之前会提前暴露ObjectFactory。但是需要注意AOP场景(使用了@Async注解的类)。
- 不能解决prototype作用域等一些特殊场景下的循环依赖。