Spring是如何解决循依赖问题?

 作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等

Spring 的循环依赖一直都是 Spring 中一个很重要的话题,一方面是 Spring 为了解决循环依赖做了很多工作,另一个方面是因为它是面试 Spring 的常客,因为他要求你看过 Spring 的源码,如果没有看过 Spring 源码你基本上是回答不了这个问题的,虽然也有一些面试经会分析这个问题,但是如果别人深究的话,你没有看过源码真心回答不了。

什么是循环依赖

从字面上来理解就是 Spring Bean 之间的依赖产生了循环,例如 A 依赖 B ,B 依赖 C,C 依赖 A,如下:

代码大致如下:

@Service
public class AService {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private CService cService;
}

@Service
public class CService {
    @Autowired
    private AService aService;
}


Spring 解决了哪些情况的循环依赖

之前码哥说到 Spring 有五种作用域:

  1. singleton:单例作用域
  2. prototype:原型作用域
  3. request:请求作用域
  4. session:会话作用域
  5. application:全局作用域

但是 Spring 只解决单例作用域(singleton)循环依赖,主要原因如下:

  • prototype:每次请求都会创建新的 Bean 实例,会形成一个死循环,同时用一次就丢,解决循环依赖成本比较大。
  • requestsessionapplication:解决他们的循环依赖需要很复杂的处理机制,可能会引入额外的性能开销和复杂性。
  • 同时 ,Spring 的设计哲学倾向于鼓励良好的编程实践,依赖循环依赖可能是设计上的问题,我们应该是采取更加简洁和更加优雅的代码结构,而不是依赖 Spring 来解决它。

同时,Spring 有三种注入方式:

  • 基于字段的注入
  • 基于 setter 方法的注入
  • 基于构造器注入

对于这三种注入方式 ,Spring 不解决全是基于构造器注入的方式 ,因为 Spring 会报错,例如我们将上面的调整为构造器注入,启动时会报如下错误 :

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  AService defined in file [/xxx/AService.class]
↑     ↓
|  BService defined in file [/xxx/BService.class]
↑     ↓
|  CService defined in file [/xxx/CService.class]
└─────┘

注:我是依赖 Spring Boot 写的测试案例

基于上面的分析,我们知道 Spring 解决循环依赖的前提条件是 :

  1. 出现循环依赖的 B 必须是单例作用域。
  2. 依赖注入的方式不能全是构造器注入的方式。

有很多文章说,Spring 只解决 setter 方法的循环依赖,这是错误的,我们来演示下:

@Service
public class AService {

    @Autowired
    private BService bService;
}

B、C 还是构造器注入,你会发现这样启动是不会报错的。

基于这个问题,码哥留一个思考题,如果 A、B、C 的注入方式是如下:

@Service
public class AService {

    private BService bService;

    @Autowired
    public AService(BService bService) {
        this.bService = bService;
    }
}

@Service
public class BService {

    @Autowired
    private CService cService;
}

@Service
public class CService {

    private AService aService;

    @Autowired
    public CService(AService aService) {
        this.aService = aService;
    }
}

会报错吗?为什么?

Spring 是怎么解决循环依赖的

Spring 解决循环依赖就靠三招:

  1. 单例模式
  2. 三级缓存
  3. 提前暴露、早起引用

单例模式前面已经分析过了,我们先看三级缓存。Spring 提供了三级缓存用来存储单例 的 Bean 实例(下面所说的 Bean 全部都为单例模式的 Bean),这三个缓存是互斥的,同一个 Bean 实例只会三级缓存中的一个中存在。三级缓存分别是:

  • 一级缓存singletonObjects
    • 用于存放完全初始化好的 Bean。
    • 当一个 Bean 被完全初始化好后(即所有的属性都被注入,所有的初始化方法都被调用),它会被放入这个缓存中。在此之后,每次请求这个 Bean 时,Spring 容器都会直接从这个缓存返回实例。
  • 二级缓存earlySingletonObjects
    • 用于存放提前暴露的 Bean 对象,即已经实例化但尚未完全初始化(未完成依赖注入和初始化方法调用)的 Bean,注意该 Bean 还处于创建中。
    • 该缓存是解决循环依赖的核心所在。当一个 Bean 正在创建的过程中,如果另外一个 Bean 需要引用它,则 Spring 为它提供该 Bean的一个早期引用,这个早期引用就存放在 earlySingletonObjects 缓存中。
  • 三级缓存singletonFactories
    • 存放 Bean 的工厂对象,用于生成 Bean 的早期引用。
    • 它是解决循环依赖的第一步。当一个 Bean 开始创建时,Spring 首先在这个缓存中放入一个工厂对象。这个工厂对象能够生成 Bean 的早期引用,当这个 Bean 需要被注入到其他 Bean 中时,就会通过这个工厂对象来创建早期引用。

三个缓存协同工作 ,以确保在应用中存在循环依赖的情况下,Spring 容器依然可以正确创建 Bean,并管理他们。关于三级缓存的详情请阅读这篇文章:什么是Spring的三级缓存?

现在跟着码哥的脚步来详细分析 Spring 是如何解决循环依赖的,为了更好地演示,我们将上面三个 Bean 循环依赖调整为两个即 A 依赖 B,B 依赖 A。

Spring 创建 Bean 的过程分为三个步骤:

  1. 实例化:AbstractAutowireCapableBeanFactory#createBean()
  2. 属性注入:AbstractAutowireCapableBeanFactory#populateBean()
  3. 初始化:AbstractAutowireCapableBeanFactory#initializeBean()

首先我们创建 A,先进行 A 对象的实例化过程 ,跟踪 AbstractAutowireCapableBeanFactory#createBean(),到 doCreateBean()

    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
        // .....

        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            // 加入到三级缓存 singletonFactories 中
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // ...
    }

earlySingletonExposure 为 true,则将 singletonFactory 保存到三级缓存 singletonFactories 中。条件为:

  • mbd.isSingleton():为单例模式
  • this.allowCircularReferences:检查配置是否允许循环引用
  • this.isSingletonCurrentlyInCreation(beanName):检查当前 Bean 是否正在创建。这是为了检测 Bean 是否处于创建的半成品状态(即已经开始创建但还没有完全初始化),这种状态的 Bean 是解决循环依赖的关键,因为他需要提前暴露给其他 Bean 引用,用来解决循环依赖 。

addSingletonFactory() 是将 singletonFactory 添加到三级缓存中:

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

singletonFactory 由 getEarlyBeanReference() 创建:

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

这个方法是一个很重要的方法,用来出来 AOP 的,我们先放在这里,后面来分析。

当 A 实例化后,就调用 populateBean() 来完成属性注入,这里开始注入 B,调用 getBean(b) ,我们一直跟踪源代码到 getSingleton(String beanName, boolean allowEarlyReference),该方法有两个参数:

  • beanName:获取 Bean 实例的名称。
  • allowEarlyReference:用于指定是否允许早期引用。
    • true:允许在 Bean 的初始化过程中提前获取引用,即使 Bean 正在创建中。
    • false:只有在 Bean 创建完成后才能获取引用

代码如下:

  @Nullable
  protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 从一级缓存中获取完整的 Bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果 singletonObject 为空,且在新建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      // 从二级缓存中获取该 Bean 的早期引用
      singletonObject = this.earlySingletonObjects.get(beanName);
      // 早期引用为空,且允许早期引用
      if (singletonObject == null && allowEarlyReference) {
        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) {
                // 从单例工厂获取单例 Bean
                // 注意这个 Bean 还只是一个早期引用
                singletonObject = singletonFactory.getObject();
                
                // 将 Bean 从三级缓存移动到二级缓存去
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
              }
            }
          }
        }
      }
    }
    return singletonObject;
  }

由于 B 还没有创建,所以这里会返回 null 。故 B 依然和 A 一样去走创建过程,也会在三级缓存中存放 Bean 工厂。当 B 完成实例化后开始属性注入,这个时候它会调用 getBean(a) 去获取 A 的实例对象,由于 A 还处于创建过程中,一级缓存没有,二级缓存也没有,但是在三级缓存 A 提前暴露了一个 Bean 工厂对象,B 可以在三级缓存中获取 A 的 Bean 工厂对象,通过 singletonFactory.getObject() 获取 A 的早期引用,完成注入,也就是这段代码:

// 从单例工厂获取单例 Bean
// 注意这个 Bean 还只是一个早期引用
singletonObject = singletonFactory.getObject();
                
// 将 Bean 从三级缓存移动到二级缓存去
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

B 完成注入后,开始初始化,最终得到一个完整 B。B 完成后,这个时候 A 得到的就是一个完整的 B 实例对象,A 完成注入,并进行初始化。整个过程如下图:

到这里循环依赖已经解决了。整个过程这张图已经详细阐述了,码哥就不过多阐述了。在上面码哥还埋了一个点,就是 getEarlyBeanReference(),我们继续。

我们知道注入到 B 中的 A 是通过 getEarlyBeanReference() 提前暴露出去的一个对象获取的,我们再看这个方法:

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

这里的参数 bean 就是已经实例化的 A 对象。

这行代码是关键:if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()),如果它为 false,这个方法等同于:

  protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    return exposedObject;
  }

返回的是一个原生的 A 对象,但是如果 if 语句为 true 呢?那么 exposedObject = bp.getEarlyBeanReference(exposedObject, beanName),调用后置处理器的 getEarlyBeanReference() ,然后真正实现这个方法的后置处理器就只有一个地方,那就是 AOP 的 AnnotationAwareAspectJAutoProxyCreator,如果我们的 A 进行了 AOP 代理 ,那么注入 B 的是 A 的代理对象而不是 A 本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值