Spring 循环依赖

简介: 本文将描述, Spring 循环依赖的过程和实现原理。 spring 版本: 5.1.14

Spring 循环依赖


在下文中我将对循环依赖的过程和实现做详细的描述, 以 Spring 的源码过程为主导逐步分析。 里面包含一些属性赋值的前置知识,可以在前面几篇文章中获取相关的信息。


Spring 解决了那些循环依赖场景


Spring 只支持单例非懒加载场景的循环依赖,不能解决构造注入,以及懒加载的循环依赖问题。


Spring 循环依赖定义


顾名思义,循环依赖就是说有 A, B 两个类在 A 中引用 B,并且在 B 中引用 A。这个就是一个简单的循环依赖。


例如: 在A类中通过构造方法注入B,在B类中通过构造方法注入。这种方式互相注入 Spring IOC 容器是无法完成的,回抛出 BeanCurrentlyInCreationException

依赖注入过程中, Bean A 与 Bean B 之间的循环依赖关系,需要其中一个 Bean 在未完成初始化之前被另外一个 Bean 注入。这就是一个类似先有鸡和先有蛋的问题。


一个简单的例子:


// A 依赖 B
@Component
public class A {
  @Autowired
  private B b;
}
// B 依赖 A
@Component
public class B {
  @Autowired
  private A a;
}


Spring 循环依赖处理过程


下面是我自己花的一个草图,前置条件是: A 类,B类互为循环依赖,当 a 先加载 b 后加载,这个时候应该是从 doCreateBean 开始逐步查找。


image.png


以上面 A 类 和 B 类的循环依赖过程来说


  1. 首先入口方法 AbstractAutowireCapableBeanFactory#doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) 以此为入口经过了一下的几个流程处理


//1. 创建 Bean 实例可以获取到一个 BeanWrapper 对象
instanceWrapper = createBeanInstance(beanName, mbd, args);
//2. 合并BeanDefinitionPostProcessor
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
//3. 属性填充
populateBean(beanName, mbd, instanceWrapper);
//4. Bean 初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);
//5. 注册 Bean 的销毁方法
registerDisposableBeanIfNecessary(beanName, bean, mbd);


  1. 循环依赖的几个步骤, (1). 在属性填充 populateBean 方法调用之前,将 instanceWrapper 放入三级缓存 singletonFactories 中,调用代码:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); 注意,这里三级缓存中存放的是 ObjectFactory 对象工厂,目的是为了解决该对象可能被代理,如果我们直接缓存该对象那么就会存在Bean对象 a 的原始对象和代理对象最终不一致的情况,具体的 AOP 代理部分我会在后续的文章中提到。首先 ()->{} 用法是一个标准的 拉姆达表达式。我们再来看看 addSingletonFactory 方法是如何实现的。


protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            //三级缓存,缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的。
            this.singletonFactories.put(beanName, singletonFactory);
            //二级缓存,缓存的是早期的bean对象。表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}


(2). 后续会经历,A类 去查找依赖 B类,然后通过 getBean 方法创建 B 类, 然后 B 类又依赖 A 类那么再会通过 getBean 方法来查找 a 类。经过这些流程我们就来到了 。AbstractBeanFactory#doGetBean 方法,然后再通过 getSingleton 来获取Bean, 我们先来看看这个方法的实现是怎么样的:


protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 单例池中获取是否存在
  Object singletonObject = this.singletonObjects.get(beanName);
  // 正在创建
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      // 二级缓存 earlySingletonObjects 存放的有可能是经过AOP增强的代理对像
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 三级缓存 singletonFactories 用于 Bean 的早期暴露以解决循环依赖
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          singletonObject = singletonFactory.getObject();
          // 放到二级缓存中
          this.earlySingletonObjects.put(beanName, singletonObject);
          // 从三级缓存中移除
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}


由于我们 A 类已经在创建中,所以我们拿到的应该是一个 A 类的 ObjectFactory 此时,Bean a 就由3级缓存 singletonFactories 进入二级缓存 earlySingletonObjects, 如果此时多次访问,那么我们可以直接在二级缓存  earlySingletonObjectsget


(3). 那么此时 Bean b 也同样要走这样的流程,最终 b 完成创建之后会来到 addSingleton(beanName, singletonObject); 因为先创建的 a 并且 a 依赖 b 所以在完成 a 创建之前需要先完成 b 的创建。我们再说说 addSingleton 方法, 它主要就是将 bean 从二,三级缓存中移除,然后放入一级缓存 singletonObjects 中。


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


Spring 循环依赖总结


  1. 三级缓存 registeredSingletons 对于我看来作用主要是用来:作为一个早期对象的暴露,他可能是一个代理工厂,也可能是一个原始对象,这里具体取决于上下文场景。


  1. 二级缓存 earlySingletonObjects 作为一个不完整的对象来暴露,此时对象没有完成属性的赋值,但是它里面的的对象可以作为自动注入的对象,注入给使用者。


  1. 一级缓存 singletonObjects 存放的一个完整的对象,标志单实例 Bean 已经完成创建。


Spring 源码解析










相关文章
|
6天前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
3天前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
4天前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
29天前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
29天前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
29天前
|
Java Spring
Spring循环依赖问题之构造器内的循环依赖如何解决
Spring循环依赖问题之构造器内的循环依赖如何解决
|
29天前
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
|
29天前
|
存储 缓存 Java
Spring循环依赖问题之循环依赖异常如何解决
Spring循环依赖问题之循环依赖异常如何解决
|
29天前
|
XML Java 数据格式
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
|
存储 缓存 Java
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?
在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。
439 0