Spring 是如何解决循环依赖的?

简介: Spring 是如何解决循环依赖的?

Spring 是如何解决循环依赖的?



循环依赖:


640.png



Spring 循环依赖有三种情况:


  1. 构造器的循环依赖,这种依赖 Spring 无法处理,直接抛出 BeanCurrentlyInCreationException 异常
  2. 单例模式下的 setter 循环依赖,可以通过三级缓存处理
  3. 非单例循环依赖,无法处理,BeanCurrentlyInCreationException 异常


构造器循环依赖


正要创建的 bean 记录在缓存中,Spring 容器架构一个正在创建的 bean 标识符放在一个 “当前创建 bean 池”中国, 因此如果在创建 Bean 过程中,如果发现已经在当前创建的 Bean 池中,则抛出 BeanCurrentlyInCreationException 异常表示循环依赖,对于创建完毕的 Bean 将从“当前创建 Bean 池”中清除。 先看个例子:


// StudentA 
public class StudentA {
    private StudentB studentB ;
    public void setStudentB(StudentB studentB) {
        this.studentB = studentB;
    }
    public StudentA() {
    }
    public StudentA(StudentB studentB) {
        this.studentB = studentB;
    }
}
// StudentB 
public class StudentB {
    private StudentC studentC ;
    public void setStudentC(StudentC studentC) {
        this.studentC = studentC;
    }
    public StudentB() {
    }
    public StudentB(StudentC studentC) {
        this.studentC = studentC;
    }
}
// StudentC
public class StudentC {
    private StudentA studentA ;
    public void setStudentA(StudentA studentA) {
        this.studentA = studentA;
    }
    public StudentC() {
    }
    public StudentC(StudentA studentA) {
        this.studentA = studentA;
    }
}

xml 配置


<bean id="a" class="com.student.StudentA">
  <constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="com.student.StudentB">
    <constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="com.student.StudentC">
    <constructor-arg index="0" ref="a"></constructor-arg>
</bean>

测试代码


public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //System.out.println(context.getBean("a", StudentA.class));
    }
}

报错如下:


caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

Setter 注入(单例)


<!--scope="singleton"(默认就是单例方式)  -->
<bean id="a" class="com.student.StudentA" scope="singleton">
 <property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.student.StudentB" scope="singleton">
 <property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.student.StudentC" scope="singleton">
 <property name="studentA" ref="a"></property>
</bean>

测试代码


public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        System.out.println(context.getBean("a", StudentA.class));
    }
}

运行时不会报错的.


Setter 注入(非单例模式)


对于 prototype 作用域的 Bean ,Spring 容器无法完成依赖注入,因为 Prototype 作用域的bean ,sring 不进行缓冲,无法提提前暴露一个创建中的Bean。会抛出异常。


<bean id="a" class="com.student.StudentA" scope="prototype">
 <property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.student.StudentB" scope="prototype">
 <property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.student.StudentC" scope="prototype">
 <property name="studentA" ref="a"></property>
</bean>

测试代码


public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //System.out.println(context.getBean("a", StudentA.class));
    }
}

报错


Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
 Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?


Spring  Bean 创建过程

640.png


  1. 实例化 Bean 对象,createBeanInstance 实例化
  2. 设置 Bean 属性,populateBean 填充属性
  3. 通过 各种 Aware 接口声明了依赖关系,则会注入 Bean 对容器基础设施层面的依赖,包括 BeanNameAware、BeanFactoryAware 和 ApplicationContextAware 分别注入 BeanID, BeanFactory 或者 ApplicationContext。
  4. 调用 BeanPostProcessor 的前置初始化方法 postProcessBeforeInitialization
  5. 如果实现了 InitializingBean 接口,会调用 afterProperties 方法。
  6. 调用 Bean 自定义的 init 方法,initializeBean 调用 xml 的 init方法
  7. 调用 BeanPostprocessor 的后缀初始方法 postProcessAfterInitialization。
  8. 创建过程完毕。


Spring 是如何解决单例的循环依赖问题的呢?


Spring 采用的三级缓存解决了单例的循环依赖问题。


三级缓存:


Spring 源码 DefaultSingletonBeanRegistry.java 中:


/** Cache of singleton objects: bean name --> bean instance */
一级缓存:维护着所有创建完成的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of early singleton objects: bean name --> bean instance */
二级缓存:维护早期暴露的Bean(只进行了实例化,并未进行属性注入)
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Cache of singleton factories: bean name --> ObjectFactory */
三级缓存:维护创建中Bean的ObjectFactory(解决循环依赖的关键)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
  Assert.notNull(beanName, "'beanName' must not be null");
  synchronized (this.singletonObjects) {
   Object oldObject = this.singletonObjects.get(beanName);
   if (oldObject != null) {
    throw new IllegalStateException("Could not register object [" + singletonObject +
      "] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
   }
      // 如果是新的bean 也会调用这个方法,这个方法是往一级缓存中set 值的 getSingleton()中也会调用
   addSingleton(beanName, singletonObject);
  }
 }
/**
 * 添加单例实例
 * 解决循环引用的问题
 * Add the given singleton factory for building the specified singleton
 * if necessary.
 * <p>To be called for eager registration of singletons, e.g. to be able to
 * resolve circular references.
 * @param beanName the name of the bean
 * @param singletonFactory the factory for the singleton object
 */
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
 Assert.notNull(singletonFactory, "Singleton factory must not be null");
 synchronized (this.singletonObjects) {
  // 一级缓存实例化 bean 中不包含 正创建的 bean
  if (!this.singletonObjects.containsKey(beanName)) {
      // 三级缓存中添加
   this.singletonFactories.put(beanName, singletonFactory);
      // 二级缓冲删除
   this.earlySingletonObjects.remove(beanName);
   this.registeredSingletons.add(beanName);
  }
 }
}

AbstractBeanFactory.doGetBean()


protected  T doGetBean(final String name, @Nullable final Class requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // 尝试通过bean名称获取目标bean对象,比如这里的A对象
  Object sharedInstance = getSingleton(beanName);
  // 我们这里的目标对象都是单例的
  if (mbd.isSingleton()) {
    // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada
    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建
    // 目标对象
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // 尝试创建目标对象
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}


getSingleton 可以这样理解:

  1. 先从一级缓冲中看有没有创建好的 bean ,有就直接返回。
  2. 如果没有,那么从二级缓存中看有没有创建 半成品的 Bean,如果有,直接返回
  3. 如果没有,从三级缓存中看下有没有创建过程中的 bean,还没有 那么通过 singletonFactory.getObject 最后到 createBean 创建。


@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回
  Object singletonObject = this.singletonObjects.get(beanName);
  // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象
  // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的
        // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品
        ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 获取目标对象的实例
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

Spring 是怎么解决单例setter注入循环依赖的?


  • Spring是通过递归的方式获取目标bean及其所依赖的bean的;
  • Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性


setter 注入是属性注入和构造器注入不一样,spring初始化是先创建bean ,然后注入属性的。单例 的setter采用三级缓存各自拿到各自的属性引用,然后再属性注入,最后各自完成实例化,不存在循环等待死锁的问题。


场景:A 依赖 B,B  依赖 A。


假设创建 A 对象的时候进入 getSingleton 方法。创建 B 的时候进入了个 doCreateBean 方法,在创建 B 还没创建完过程中,会在三级缓存 singletonFactories 先放一个 B,此时,如果创建  A 对象时,一级缓存没有B,从二级缓存找,二级缓存没有,从三级别缓存中找到就可以直接返回,并将自身A放入一级缓存中。

此时 B 在初始化过程中,从一级缓存中取到了A,这样B就拿到了A的引用,这样也B也就在拿到A的过程中完成了初始化。


protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
  throws BeanCreationException {
  // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories
    // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是
    // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是
    // 目标bean直接返回
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }
  try {
    // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,
    // 就会递归的调用getBean()方法尝试获取目标bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    // 省略...
  }
  return exposedObject;
}


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