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 源码解析










相关文章
|
1月前
|
缓存 架构师 Java
图解 Spring 循环依赖,一文吃透!
Spring 循环依赖如何解决,是大厂面试高频,本文详细解析,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
图解 Spring 循环依赖,一文吃透!
|
19天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
39 2
|
3月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
231 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2月前
|
缓存 Java Spring
源码解读:Spring如何解决构造器注入的循环依赖?
本文详细探讨了Spring框架中的循环依赖问题,包括构造器注入和字段注入两种情况,并重点分析了构造器注入循环依赖的解决方案。文章通过具体示例展示了循环依赖的错误信息及常见场景,提出了三种解决方法:重构代码、使用字段依赖注入以及使用`@Lazy`注解。其中,`@Lazy`注解通过延迟初始化和动态代理机制有效解决了循环依赖问题。作者建议优先使用`@Lazy`注解,并提供了详细的源码解析和调试截图,帮助读者深入理解其实现机制。
71 1
|
3月前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
46 4
|
4月前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
4月前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
4月前
|
Java Spring 容器
循环依赖难破解?Spring Boot神秘武器@RequiredArgsConstructor与@Lazy大显神通!
【8月更文挑战第29天】在Spring Boot应用中,循环依赖是一个常见问题。当两个或多个Bean相互依赖形成闭环时,Spring容器会陷入死循环。本文通过对比@RequiredArgsConstructor和@Lazy注解,探讨它们如何解决循环依赖问题。**@RequiredArgsConstructor**:通过Lombok生成包含final字段的构造函数,优先通过构造函数注入依赖,简化代码但可能导致构造函数复杂。**@Lazy**:延迟Bean的初始化,直到首次使用,打破创建顺序依赖,增加灵活性但可能影响性能。根据具体场景选择合适方案可有效解决循环依赖问题。
166 0
|
5月前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
4月前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作