Skip to content

Spring 循环依赖

本文介绍Spring中有关循环依赖的问题。

1. 循环依赖流程讲解

Spring使用三级缓存解决循环依赖:

  • 一级缓存 singletonObjects:存放已创建完成,已依赖注入的Bean;
  • 二级缓存 earlySingletonObjects:存放已创建完成,但是未完成依赖注入的Bean;
  • 三级缓存 singletonFactories:存放ObjectFactory,可以通过getObject()获取Bean;
image-20250711223326568

当我们从容器中获取Bean时,会先检查三级缓存中有没有Bean:

  1. 先检查一级缓存中有没有Bean,如果有,直接返回;如果没有,进行下一步;
  2. 再检查二级缓存中有没有Bean,如果有,直接返回;如果没有,进行下一步;
  3. 最后检查三级缓存中有没有Bean
    1. 如果有,从三级缓存中获取ObjectFactory,调用getObject()方法获取Bean,然后将这个Bean放入二级缓存,将这个ObjectFactory从三级缓存中删除;
    2. 如果没有,返回null

Spring中的循环依赖解决流程,首先以下面的代码为例:

java
@Component
class A{
  	@Autowired
  	private B b;
}

@Component
class B{
    @Autowired
  	private A a;
}

假设现在要从容器中获取A实例对象:

  • 首先,容器中没有A对象,通过反射创建出A对象,注意,此时A对象中的属性b为null;

  • 然后,将A对象封装为ObjectFactory,放入三级缓存singletonFactories中;

    image-20250711223455071
  • 然后,开始对A对象进行依赖注入,发现需要B对象;

    • 在容器中没有B没有,通过反射创建出B对象,同样地,此时B对象中的属性a为null;

    • 同样将B对象封装为ObjectFactory,放入三级缓存singletonFactories中;

      image-20250711223530578
    • 然后,开始对B对象进行依赖注入;

    • 发现B对象依赖A对象,此时从容器中获取A对象;

    • 遵循前面的查找规则,最终A对象从三级缓存移到二级缓存;

      image-20250711223608827
    • 最后,B对象的创建和依赖注入流程完成,此时B对象创建完成,最后,会将B对象放入一级缓存,并且把B对象从二级、三级缓存中删除;

      image-20250711223642275
  • 当经过B对象的创建过程后,容器中就有B对象了,此时可以在一级缓存中找到,将其注入到A对象中;

  • 最后,A对象创建完成,将A对象放入一级缓存,并且将A对象从二级、三级缓存中删除;

    image-20250711223712016

2. 循环依赖源码分析

2.1 从三级缓存中查找Bean

从三级缓存中查找Bean的方法是getSingleton()

txt
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

源码如下:

java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 首先从一级缓存中查找Bean
  Object singletonObject = this.singletonObjects.get(beanName);
  // 在一级缓存中为空,并且当前BeanName所对应的Bean是在创建过程中
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    // 从二级缓存中获取Bean
    singletonObject = this.earlySingletonObjects.get(beanName);
    // 在二级缓存中为空,并且允许获取Bean的早期引用(allowEarlyReference为true)
    if (singletonObject == null && allowEarlyReference) {
      // 加锁
      if (!this.singletonLock.tryLock()) {
        return null;
      }
      try {
        // 为了防止其他线程创建Bean,加锁后需要再判断一下
        // 先查找一级缓存
        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) {
              // 在三级缓存中找到了,调用getObject()方法获取Bean
              singletonObject = singletonFactory.getObject();
              // 从三级缓存中删除ObjectFactory
              if (this.singletonFactories.remove(beanName) != null) {
                // 加入到二级缓存
                this.earlySingletonObjects.put(beanName, singletonObject);
              }
              else {
                // 如果从三级缓存中删除ObjectFactory失败,表示可能有其他线程在锁外修改了singletonFactories 和 singletonObjects 
                // 再次判断是为了确保当前线程拿到的是最新状态下的数据,避免因并发导致的数据不一致问题。
                singletonObject = this.singletonObjects.get(beanName);
              }
            }
          }
        }
      }
      finally {
        // 释放锁
        this.singletonLock.unlock();
      }
    }
  }
  // 返回获取到的Bean
  return singletonObject;
}

2.2 将Bean加入到一级缓存中

将Bean加入到一级缓存的方法是addSingleton()

txt
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingleton

源码如下:

java
protected void addSingleton(String beanName, Object singletonObject) {
  // 将Bean加入到一级缓存中,singletonObject就是Bean
  Object oldObject = this.singletonObjects.putIfAbsent(beanName, singletonObject);
  if (oldObject != null) {
    // 重复加入,报错
    throw new IllegalStateException("Could not register object [" + singletonObject +
        "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
  }
  // 从三级缓存中移除
  this.singletonFactories.remove(beanName);
  // 从二级缓存中移除
  this.earlySingletonObjects.remove(beanName);
  // 已注册的Bean记录
  this.registeredSingletons.add(beanName);

  Consumer<Object> callback = this.singletonCallbacks.get(beanName);
  if (callback != null) {
    callback.accept(singletonObject);
  }
}

2.3 创建Bean

创建Bean的关键方法:

txt
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

主要逻辑如下:

  • 创建Bean:
java
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
  instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
  instanceWrapper = createBeanInstance(beanName, mbd, args);
}
Object bean = instanceWrapper.getWrappedInstance();
  • 将创建的Bean包装为ObjectFactory,放入三级缓存:
java
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
  • 依赖注入与初始化
java
Object exposedObject = bean;
try {
  // 依赖注入
  populateBean(beanName, mbd, instanceWrapper);
  // 初始化
  exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
  if (ex instanceof BeanCreationException bce && beanName.equals(bce.getBeanName())) {
    throw bce;
  }
  else {
    throw new BeanCreationException(mbd.getResourceDescription(), beanName, ex.getMessage(), ex);
  }
}
  • 最后,创建Bean和依赖注入完成后,会将Bean放入一级缓存

    txt
    org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

    在以上方法中有如下代码:

    java
    if (newSingleton) {
      try {
        addSingleton(beanName, singletonObject);
      }
      catch (IllegalStateException ex) {
        Object object = this.singletonObjects.get(beanName);
        if (singletonObject != object) {
          throw ex;
        }
      }
    }

3. 为什么需要第三级缓存

三级缓存的真正作用:处理 AOP 代理。两级缓存的问题在于,它无法优雅地处理 AOP(面向切面编程)代理

想象一下 Bean AB 相互依赖,并且 A 需要被 AOP 代理(例如,A 上有 @Transactional 注解)。

  • 首先创建 A,Spring 实例化 A;

  • 现在,如果 Spring 直接将 A 的原始实例放入 earlySingletonObjects

  • 然后对A对象进行依赖注入,发现需要B对象,进行B对象的创建,发现 B 依赖 A,B 获取并注入的是 A 的原始实例;

  • 当 A 完成初始化并准备被放入 singletonObjects 时,Spring 才会对 A 进行 AOP 代理;

    AOP代理是在组件初始化后才进行的,请参考 AOP

  • 问题来了:此时 B 中注入的是 A 的原始实例,而不是 A 的代理实例。这会导致 B 调用 A 的方法时,A 上的 AOP 功能(如事务)不会生效,这是不正确的。

为了确保注入给其他 Bean 的始终是最终的 Bean 实例(可能是原始实例,也可能是代理实例),singletonFactories 应运而生。

为什么有了三级缓存,就能保证注入其他Bean的就是最终Bean实例呢?答案就在于向第三级缓存中加入的ObjectFactory是什么:

java
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
    isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

getEarlyBeanReference() 代码如下:

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

bp.getEarlyBeanReference() 如下:

txt
org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference
java
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
  return bean;
}

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator是其实现类:

image-20250711233827313

这就是AOP的自动代理类呀!

方法如下:

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

看到了熟悉的wrapIfNecessary()方法,这个方法会返回代理对象。

所以,有了三级缓存,当B对象从三级缓存中找到A对象的ObjectFactory后,调用getObject()方法,会调用wrapIfNecessary()方法,最后会返回代理对象。

4. 能不能去掉第二级缓存

前面分析了第三级缓存的必要性,那能不能去掉第二级缓存呢?

如果没有 earlySingletonObjects 会发生什么?假设我们只有 singletonObjectssingletonFactories

考虑 Bean A 和 Bean B 循环依赖,并且 Bean A 需要被 AOP 代理

  1. 实例化 A:Spring 实例化 Bean A。

  2. A 放入 singletonFactories:Spring 创建 Bean A 的 ObjectFactory 并将其放入 singletonFactories

  3. A 填充属性,依赖 B:Bean A 开始填充属性,发现依赖 Bean B,于是去创建 Bean B。

  4. 创建 B,依赖 A:Bean B 开始创建。当 Bean B 发现依赖 Bean A 时:

    • 它会检查 singletonObjects(没有 Bean A)。

    • 它会去 singletonFactories 查找 Bean A 的 ObjectFactory。找到了!

    • Spring 调用 Bean A 的 ObjectFactory 来获取 Bean A 的早期引用。在这个过程中,如果 Bean A 需要 AOP 代理,代理对象(A')会被创建

    • 问题来了:如果没有 earlySingletonObjects,这个 A' 应该放在哪里呢?

      如果直接放入 singletonObjects,那么 Bean A 就被标记为“完成”了,但它实际上还没完成属性填充和初始化方法。

      如果不存,那么下次另一个 Bean C 也依赖 Bean A,它会再次从 singletonFactories 获取,并再次创建代理对象,这显然是浪费的。

所以earlySingletonObjects 的作用是避免重复创建代理和性能优化,earlySingletonObjects 作为一个临时性的中转站和缓存,用来存放那些已经通过 ObjectFactory 生成的早期 Bean 引用(可能是原始实例,也可能是代理实例)

5. 循环依赖的局限性

Spring只能处理set依赖注入的循环依赖,无法处理构造方法注入的循环依赖。

例如,下面这种情况会报错

java
@Component
public class ServiceX {
    private final ServiceY serviceY;

    @Autowired
    public ServiceX(ServiceY serviceY) {
        this.serviceY = serviceY;
    }
}

@Component
public class ServiceY {
    private final ServiceX serviceX;

    @Autowired
    public ServiceY(ServiceX serviceX) {
        this.serviceX = serviceX;
    }
}

如果要解决构造方法注入的循环依赖,可以使用@Lazy注解:

java
@Component
public class ServiceX {
    private final ServiceY serviceY;

    @Autowired
    public ServiceX(@Lazy ServiceY serviceY) {
        this.serviceY = serviceY;
    }
}

@Component
public class ServiceY {
    private final ServiceX serviceX;

    @Autowired
    public ServiceY(ServiceX serviceX) {
        this.serviceX = serviceX;
    }
}