侧边栏壁纸
博主头像
再见理想博主等级

只争朝夕,不负韶华

  • 累计撰写 112 篇文章
  • 累计创建 64 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

Spring 如何处理循环依赖

再见理想
2022-10-26 / 0 评论 / 1 点赞 / 571 阅读 / 5,076 字

一,什么是循环依赖

循环依赖,其实就是循环引用,就是两个或者两个以上的 bean 互相引用对方,最终形成一个闭环,如 A 依赖 B,B 依赖 C,C 依赖 A。如下图所示:

Spring 循环依赖的主要场景:

Spring 只解决 scope 为 singleton 的循环依赖。

对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖;对于scope 为 prototype 的 bean ,Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。

为什么 Spring 不处理 prototype bean 呢?其实如果理解 Spring 是如何解决 singleton bean 的循环依赖就明白了。我们先来关注 Spring 是如何解决 singleton bean 的循环依赖的。

可以带着以下问题阅读此文:

  • Spring 的 Bean 是如何创建出来的?
  • 为什么需要二级缓存?
  • 为什么需要三级缓存?

二,Spring 的 Bean 是如何创建出来的?

先说如何解决循环依赖之前,先来了解一下一个Bean创建的大致流程。为什么要说Bean的创建过程,因为循环依赖主要是发生在Bean创建的过程中,知道Bean是如何创建的,才能更好的理解三级缓存的作用。

大概的流程可以参考下图:

来说一下每个阶段干了什么事。

  • BeanDefinition的读取阶段:我们在往Spring容器注入Bean的时候,一般会通过比如xml方式,@Bean注解的方式,@Component注解的方式,其实不论哪一种,容器启动的时候都会去解析这些配置,然后为每个Bean生成一个对应的BeanDefinition,这个BeanDefinition包含了这个Bean的创建的信息,Spring就是根据BeanDefinition去决定如何创建一个符合你要求的Bean
  • Bean的实例化阶段:这个阶段主要是将你配置的Bean根据Class的类型创建一个对象出来
  • Bean的属性赋值阶段:这个阶段主要是用来处理属性的赋值,比如@Autowired注解的生效就是在这个阶段的
  • Bean的初始化阶段:这个阶段主要是回调一些方法,比如你的类实现了InitializingBean接口,那么就会回调afterPropertiesSet方法,同时动态代理也是在这个阶段完成的。

从这可以看出,一个Spring Bean 的生成要分为很多的阶段,只有这些事都处理完了,这个 Bean 才是完完全全创建好的Bean,也就是我们可以使用的 Bean。

三,Spring 如何解决循环依赖

3.1,三级缓存

ingletonObjects、earlySingletonObjects、singletonFactories 就是 Spring 解决 singleton bean 的关键因素所在,称他们为三级缓存

  • 一级缓存:singletonObjects,存放的是已实例化、初始化好的单例 bean;
  • 二级缓存:earlySingletonObjects,存放的是早期半成品(未初始化完)的 bean;
  • 三级缓存:singletonFactories,存放的是创建早期半成品(未初始化完)的 bean 的 factory,最终添加到二级缓存 earlySingletonObjects 中。

三者定义如下:

// DefaultSingletonBeanRegistry.java
        
/**
 * Cache of singleton objects: bean name to bean instance.
 * 一级缓存,存放的是单例 bean 的映射。
 * 注意,这里的 bean 是已经创建完成的。对应关系为 bean name --> bean instance
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
/**
 * Cache of early singleton objects: bean name to bean instance.
 * 二级缓存,存放的是早期半成品(未初始化完)的 bean,对应关系也是 bean name --> bean instance。
 * 它与 singletonObjects 区别在于, 它自己存放的 bean 不一定是完整。
 * 这个 Map 也是【循环依赖】的关键所在。
 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
/**
 * Cache of singleton factories: bean name to ObjectFactory.
 * 三级缓存,存放的是 ObjectFactory,可以理解为创建早期半成品(未初始化完)的 bean 的 factory ,最终添加到二级缓存 earlySingletonObjects 中。对应关系是 bean name --> ObjectFactory
 * 这个 Map 也是【循环依赖】的关键所在。
 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

3.2,getSingleton

我们先从加载 bean 最初始的方法 AbstractBeanFactory#doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) 方法开始。

#doGetBean(...) 方法中,首先会根据 beanName 从单例 bean 缓存中获取,如果不为空则直接返回。代码如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;

    // Eagerly check singleton cache for manually registered singletons.
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        ...

下面看看 getSingleton 方法:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存 singletonObjects 中加载 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 缓存中的 bean 为空,且当前 bean 正在创建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加锁
        synchronized (this.singletonObjects) {
            // 从二级缓存 earlySingletonObjects 获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects 中没有,且允许提前创建
            if (singletonObject == null && allowEarlyReference) {
                // 从三级缓存 singletonFactories 中获取对应的 ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 获得 bean
                    singletonObject = singletonFactory.getObject();
                    // 添加 bean 到 earlySingletonObjects 中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 从 singletonFactories 中移除对应的 ObjectFactory
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

这里,我们已经通过 getSingleton(String beanName, boolean allowEarlyReference) 方法,看到他们是如何配合的。详细分析该方法之前,提下其中的 isSingletonCurrentlyInCreation(String beanName) 方法和 allowEarlyReference 变量:

  • isSingletonCurrentlyInCreation(String beanName) 方法:判断当前 singleton bean 是否处于创建中。bean 处于创建中,也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 Spring 解决 bean 循环依赖的理念相辅相成。因为 Spring 解决 singleton bean 的核心就在于提前曝光 bean
  • allowEarlyReference 变量:从字面意思上面理解就是允许提前拿到引用。其实真正的意思是,是否允许从 singletonFactories 缓存中通过 #getObject() 方法,拿到对象。为什么会有这样一个字段呢?原因就在于 singletonFactories 才是 Spring 解决 singleton bean 的诀窍所在,这个我们后续分析。

这样,就从三级缓存升级到二级缓存了。😈 所以,二级缓存存在的意义,就是缓存三级缓存中的 ObjectFactory 的 #getObject() 方法的执行结果,提早曝光的单例 Bean 对象。

3.3,addSingletonFactory

上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?一直往下跟会发现在 AbstractAutowireCapableBeanFactory#doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) 方法中,有这么一段代码:

// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() // 单例模式
        && this.allowCircularReferences // 运行循环依赖
        && isSingletonCurrentlyInCreation(beanName)); // 当前单例 bean 是否正在被创建
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // 提前将创建的 bean 实例加入到 singletonFactories 中
    // <X> 这里是为了后期避免循环依赖
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

当一个 Bean 满足三个条件时,则调用 #addSingletonFactory(...) 方法,将它添加到缓存中。条件如下:

  • 单例
  • 运行提前暴露 bean
  • 当前 bean 正在创建中

#addSingletonFactory(String beanName, ObjectFactory singletonFactory) 方法,代码如下:

// DefaultSingletonBeanRegistry.java

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

从这段代码我们可以看出,singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。同时这段代码发生在 #createBeanInstance(...) 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了。所以 Spring 在这个时候,选择将该对象提前曝光出来让大家认识认识。

另外,#getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) 方法也非常重要,这里会创建早期初始化 Bean 可能存在的 AOP 代理等等。代码如下:

// AbstractAutowireCapableBeanFactory.java

/**
 * 对创建的早期半成品(未初始化)的 Bean 处理引用
 * 例如说,AOP 就是在这里动态织入,创建其代理 Bean 返回
 *
 * Obtain a reference for early access to the specified bean,
 * typically for the purpose of resolving a circular reference.
 * @param beanName the name of the bean (for error handling purposes)
 * @param mbd the merged bean definition for the bean
 * @param bean the raw bean instance
 * @return the object to expose as bean reference
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

这也是为什么需要三级缓存的原因:三级缓存的作用是为了解决Bean依赖注入时发生的循环依赖问题, 如果不需要AOP,那么只需二级缓存即可实现;如果有AOP,其实二级缓存也能实现,但是会打破Bean的生命周期,不符合spring的原则,因为必须在初始化步骤前对AOP的Bean进行后置处理等初始化操作。所有spring引入了三级缓存,解决AOP代理问题。

另外,这里在推荐一篇《Spring循环依赖三级缓存是否可以减少为二级缓存?》文章,解释的也非常不错。

3.4,addSingleton

介绍到这里我们发现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 DefaultSingletonBeanRegistry 中,可以发现这个 #addSingleton(String beanName, Object singletonObject) 方法,代码如下:

// DefaultSingletonBeanRegistry.java

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

添加至一级缓存,同时从二级、三级缓存中删除。这个方法在我们创建 bean 的链路中有哪个地方引用呢?在 #doGetBean(...) 方法中,处理不同 scope 时,如果是 singleton,则调用 #getSingleton(...) 方法,如下图:

我们关注 #getSingleton(String beanName, ObjectFactory singletonFactory) 方法,代码如下:

// AbstractBeanFactory.java

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //....
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            //.....
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

四,创建Bean完整流程

当调用 getBean(beanA) 方法去获取 beanA,Spring 会调用 getSingleton(beanA) 方法从尝试从各级缓存获取 beanA,如果能获取到,则返回;如果获取不到,调用 getSingleton(beanAsingletonFactory) 方法开始创建 beanA 实例,接着调用 doCreateBean(beanA) 方法将 beanA 工厂对象添加到三级缓存中。接着调用 populateBean(beanA) 方法进行属性填充。

这时发现依赖 beanB,调用 getBean(beanB) 方法去获取 beanB,重走上面的流程。当属性填充时发现 beanB 也依赖着 beanA,这时调用 getBean(beanA) 方法去获取 beanA。因为这时,beanA 已经提前暴露在三级缓存中,可以从三级缓存中拿到 beanA 的实例,然后将 beanA 加入到二级缓存中。然后对beanB初始化,完成对 beanB 的注入。

beanA 属性填充完成后,调用 initializeBean(beanA) 进行初始化。完成后,调用addsingleton(beanB,singletonObject) 方法,将 beanA 实例加入到一级缓存即单例池中,同时从二三级缓存中删除,完成 beanA 的创建。

创建 Bean 的链路图如下:

五,不能解决循环依赖的场景

5.1,构造器注入

构造器注入的示例代码如下:

@Service
public class OrderService {

  private TradeService tradeService;

  public OrderService(TradeService tradeService) {
    this.tradeService = tradeService;
  }
}

@Service
public class TradeService {

  private OrderService orderService;

  public TradeService(OrderService orderService) {
    this.orderService = orderService;
  }
}

构造器注入无法加入到第三级缓存当中,Spring 框架中的三级缓存在此场景下无用武之地,所以只能抛出异常,整体流程如下:

5.2,多例的循环依赖

多实例 Bean 是每次调用一次 getBean 都会执行一次构造方法并且未属性赋值,根本没有三级缓存,因此无法解决循环依赖。

5.3,@Async 为什么导致循环依赖

很多时候我们在项目中为了实现异步的方式,都会使用@Async注解来实现。但是当我们将@Async运用在循环依赖中时,会发现启动报循环依赖错误:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] 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. 

spring 默认是能解决单例情况下的循环依赖的,但是回过头来,那为什么加了 @Async 注解会报错,代码如下:

@Service 
public class A { 
    @Autowired 
    private B b; 
    
    @Async 
    public void test () {} 
} 

@Service 
public class B { 
    @Autowired 
    private A a; 
} 

流程图参考:

在创建 A 对象时,填充属性前,会将 A 对象提前暴露在三级缓存 singletonFactories,填充对象A属性时,发现依赖了 B 对象,则开始创建 B 对象。同理,在创建 B 对象时,发现需要依赖 A 对象,此时则会尝试从缓存中获取对象A ,调用 getEarlyBeanReference 从三级缓存中获取 A 对象实例,然后将 bean 的半成品单例对象,放入到二级缓存 earlySingletonObjects 中。然后将 A 放入到对象 B 中,对象 B 完成创建、初始化后,继续 A 的流程,将对象 B 注入到对象 A 属性中。对象 A 进行初始化。

到现在还没发现什么问题,下面看看问题是不是出在 A 初始化过程中。

// AbstractAutowireCapableBeanFactory.doCreateBean 
// 初始化bean 
exposedObject = initializeBean(beanName, exposedObject, mbd); 
 
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) { 
   if (System.getSecurityManager() != null) { 
      AccessController.doPrivileged((PrivilegedAction<Object>) () -> { 
         invokeAwareMethods(beanName, bean); 
         return null; 
      }, getAccessControlContext()); 
   } 
   else { 
      // spring在实例化时,最终会调用感知接口中的方法,将spring容器内相应的组件注入到bean的实例中。 
      invokeAwareMethods(beanName, bean); 
   } 
   // 上面的步骤是不会改变bean对象的,所以可以直接跳过 
   Object wrappedBean = bean; 
   if (mbd == null || !mbd.isSynthetic()) { 
      // 1)bean初始化前,通过bean后置处理(before)调整下bean的实例 
      wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); 
   } 
   try { 
      // 执行bean的初始化 
      invokeInitMethods(beanName, wrappedBean, mbd); 
   } 
   catch (Throwable ex) { 
      throw new BeanCreationException( 
            (mbd != null ? mbd.getResourceDescription() : null), 
            beanName, "Invocation of init method failed", ex); 
   } 
   if (mbd == null || !mbd.isSynthetic()) { 
      // 2)bean初始化后,执行bean的后置处理器(after) 
      wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); 
   } 
   return wrappedBean; 
} 

进入 after 后置处理器瞧瞧,我们发现又是执行各种后置处理器,主要看下 AsyncAnnotationBeanPostProcessors 后置处理器。

// AbstractAdvisingBeanPostProcessor 
public Object postProcessAfterInitialization(Object bean, String beanName) { 
    // 判断advisor通知器是否不为空、判断bean是不是实现了AopInfrastructureBean 
    if (this.advisor != null && !(bean instanceof AopInfrastructureBean)) { 
        // 判断bean是不是实现了Advised,我们这里bean并不是切面的形式,所以条件不成立 
        if (bean instanceof Advised) { 
            Advised advised = (Advised)bean; 
            if (!advised.isFrozen() && this.isEligible(AopUtils.getTargetClass(bean))) { 
                if (this.beforeExistingAdvisors) { 
                    advised.addAdvisor(0, this.advisor); 
                } else { 
                    advised.addAdvisor(this.advisor); 
                } 
 
                return bean; 
            } 
        } 
        // 此时会进入到这里,注意这里面源码进行了取反 
        // 这里其实就是判断一下bean是否能匹配到对应类型的advisor 
        if (this.isEligible(bean, beanName)) { 
            ProxyFactory proxyFactory = this.prepareProxyFactory(bean, beanName); 
            if (!proxyFactory.isProxyTargetClass()) { 
                this.evaluateProxyInterfaces(bean.getClass(), proxyFactory); 
            } 
 
            proxyFactory.addAdvisor(this.advisor); 
            this.customizeProxyFactory(proxyFactory); 
            // 重点:创建了一个代理对象并返回。 
            return proxyFactory.getProxy(this.getProxyClassLoader()); 
        } else { 
            return bean; 
        } 
    } else { 
        return bean; 
    } 
} 

我们通过源码发现 AsyncAnnotationBeanPostProcessors 返回的是一个代理对象 bean。此时我们已经发现了注入到 B 对象的 A 属性是原始的实例,但是 A 初始化后已经是一个代理对象实例了,注入到 B 对象中的 A 属性和 A 初始化后的对象不是同一个对象!因为 spring 默认是单例,这肯定会出问题。初始化完后,发现spring还会进行一次对比,源码如下:

// AbstractAutowireCapableBeanFactory.doCreateBean 
if (earlySingletonExposure) { 
   // 依然从缓存中获取,注意这里第二个参数是false,也就是说只能从一级缓存、二级缓存中获取 
   // 因为此时还未放入一级缓存,所以肯定是没有的,只能从二级缓存中获取 
   Object earlySingletonReference = getSingleton(beanName, false); 
   if (earlySingletonReference != null) { 
      // 这里会进行一个比较,看二级缓存中的bean实例是否与初始化后的bean实例相等,此时发现并不相等 
      if (exposedObject == bean) { 
         exposedObject = earlySingletonReference; 
      } 
      // 接下来就会判断这个bean是否有其他bean进行依赖,如果有则说明注入到其他bean的依赖不是最终包装过后的bean 
      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."); 
         } 
      } 
   } 
} 

至此,问题的原因已经找到了。那可以怎么解决?可以通过懒加载的形式:

public class A { 
    @Autowired 
    @Lazy
    private B b; 
    
    @Async 
    public void test () {} 
} 

六,小结

至此,Spring 关于 singleton bean 循环依赖已经分析完毕了。所以我们基本上可以确定 Spring 解决循环依赖的方案了:

  • Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中)。
  • 这样,一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 #getObject() 方法来获取了。

流程图参考:

到这里,关于 Spring 解决 bean 循环依赖就已经分析完毕了。最后来描述下循环依赖 Spring 解决的过程:

  • 首先 A 完成初始化第一步并将自己提前曝光出来,添加至三级缓存 singletonFactories ,在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来;
  • 然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来;
  • 这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中,通过 ObjectFactory 提前曝光,所以可以通过 ObjectFactory#getObject() 方法来拿到 A 对象,并将 A 对象添加至二级缓存,从三级缓存删除。C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中。
  • 回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了。
1

评论区