首页 > 其他分享 >Bean的循环依赖问题

Bean的循环依赖问题

时间:2023-06-06 12:32:04浏览次数:28  
标签:依赖 name Wife Bean 循环 public Husband

1. 什么是Bean的循环依赖  54

A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。

比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。

Bean的循环依赖问题_赋值

package com.powernode.spring6.bean;

/**
 *  什么是Bean的循环依赖  54
 * 丈夫类  54
 **/
public class Husband {
    private String name;
    private Wife wife;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
package com.powernode.spring6.bean;

/**
 *  什么是Bean的循环依赖  54
 * 妻子类
 **/
public class Wife {
    private String name;
    private Husband husband;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

2. singleton下的set注入产生的循环依赖  54

我们来编写程序,测试一下在singleton+setter的模式下产生的循环依赖,Spring是否能够解决?

<!--singleton + setter模式下的循环依赖是没有任何问题的。-->
    <!--singleton表示在整个Spring容器当中是单例的,独一无二的对象。-->
    <!--
        在singleton + setter模式下,为什么循环依赖不会出现问题,Spring是如何应对的?
            主要的原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
                第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行 “曝光”【不等属性赋值就曝光】
                第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法。)。

            核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。

        注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。
    -->
    <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
        <property name="name" value="小花"/>
        <property name="husband" ref="husbandBean"/>
    </bean>

//singleton + setter模式下的循环依赖是没有任何问题的。
    @Test
    public void testCD(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        System.out.println(husbandBean);

        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(wifeBean);
    }

Bean的循环依赖问题_赋值_02

通过测试得知:在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。

2.1 在singleton + setter模式下,为什么循环依赖不会出现问题,Spring是如何应对的?55

主要的原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:

         第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行 “曝光”【不等属性赋值就曝光】

          第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法。)。

核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成的。

注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。

3. prototype下的set注入产生的循环依赖  56

注意:当两个bean的scope都是prototype的时候,才会出现异常。如果其中任意一个是singleton的,就不会出现异常。

我们再来测试一下:prototype+set注入的方式下,循环依赖会不会出现问题?

<!--在prototype + setter模式下的循环依赖,存在问题,会出现异常!-->
    <!--BeanCurrentlyInCreationException 当前的Bean正在处于创建中异常。。。-->
    <!-- 注意:当两个bean的scope都是prototype的时候,才会出现异常。
    如果其中任意一个是singleton的,就不会出现异常。-->
    <bean id="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
        <property name="name" value="张三"/>
        <property name="wife" ref="wifeBean"/>
    </bean>

    <bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
        <property name="name" value="小花"/>
        <property name="husband" ref="husbandBean"/>
    </bean>
//在prototype + setter模式下的循环依赖,存在问题,会出现异常!  56
    @Test
    public void testCD1(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");

        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        System.out.println(husbandBean);

        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        System.out.println(wifeBean);
    }

Bean的循环依赖问题_Bean循环依赖_03

3.1 执行测试程序:发生了异常,异常信息如下:56

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

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)

... 44 more

翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?

通过测试得知,当循环依赖的所有Bean的scope="prototype"的时候,产生的循环依赖,Spring是无法解决的,会出现BeanCurrentlyInCreationException异常。

大家可以测试一下,以上两个Bean,如果其中一个是singleton,另一个是prototype,是没有问题的。

3.2 为什么两个Bean都是prototype时会出错呢? 56

Bean的循环依赖问题_赋值_04

4. singleton下的构造注入产生的循环依赖  57

我们再来测试一下singleton + 构造注入的方式下,spring是否能够解决这种循环依赖。

package com.powernode.spring6.bean2;

/**
 * singleton下的构造注入产生的循环依赖  57
 * 丈夫类
 **/
public class Husband {
    private String name;
    private Wife wife;

    public Husband(String name, Wife wife) {
        this.name = name;
        this.wife = wife;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
package com.powernode.spring6.bean2;

/**
 * singleton下的构造注入产生的循环依赖  57
 * 妻子类
 **/
public class Wife {
    private String name;
    private Husband husband;

    public Wife(String name, Husband husband) {
        this.name = name;
        this.husband = husband;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

spring2.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--    singleton下的构造注入产生的循环依赖  57-->
    <!--构造注入,这种循环依赖有没有问题?-->
    <!--注意:基于构造注入的方式下产生的循环依赖也是无法解决的,所以编写代码时一定要注意。-->
    <bean id="h" scope="singleton" class="com.powernode.spring6.bean2.Husband">
        <constructor-arg index="0" value="张三"></constructor-arg>
        <constructor-arg index="1" ref="w"></constructor-arg>
    </bean>

    <bean id="w" scope="singleton" class="com.powernode.spring6.bean2.Wife">
        <constructor-arg index="0" value="小花"></constructor-arg>
        <constructor-arg index="1" ref="h"></constructor-arg>
    </bean>

</beans>
//singleton下的构造注入产生的循环依赖  57
    @Test
    public void testCD2(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");

        Husband husbandBean = applicationContext.getBean("h", Husband.class);
        System.out.println(husbandBean);

        Wife wifeBean = applicationContext.getBean("w", Wife.class);
        System.out.println(wifeBean);
    }

Bean的循环依赖问题_spring_05

和上一个测试结果相同,都是提示产生了循环依赖,并且Spring是无法解决这种循环依赖的。

为什么呢?

主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。

5.  Spring解决循环依赖的机理  58

5.1 Spring为什么可以解决set + singleton模式下循环依赖?58

根本的原因在于:这种方式可以做到将“实例化Bean”和“给Bean属性赋值”这两个动作分开去完成。

实例化Bean的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean对象“曝光”给外界。

给Bean属性赋值的时候:调用setter方法来完成。

两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。

也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean全部实例化完成之后,以后我们再慢慢的调用setter方法给属性赋值。这样就解决了循环依赖的问题。

那么在Spring框架底层源码级别上是如何实现的呢?请看:

Bean的循环依赖问题_实例化_06

在以上类中包含三个重要的属性:

源码分析:

DefaultSingletonBeanRegistry类中有三个比较重要的缓存:

 singletonObjects                  一级缓存

 earlySingletonObjects             二级缓存

> singletonFactories      三级缓存

    这三个缓存都是Map集合。

    Map集合的key存储的都是bean的name(bean id)。

    一级缓存存储的是:单例Bean对象。完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。

    二级缓存存储的是:早期的单例Bean对象。这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。

    三级缓存存储的是:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。这个集合中存储的是,创建该单例对象时对应的那个单例工厂对象。

我们再来看,在该类中有这样一个方法addSingletonFactory(),这个方法的作用是:将创建Bean对象的ObjectFactory对象提前曝光。

Bean的循环依赖问题_赋值_07

再分析下面的源码:

Bean的循环依赖问题_实例化_08

从源码中可以看到,spring会先从一级缓存中获取Bean,如果获取不到,则从二级缓存中获取Bean,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory对象,通过ObjectFactory对象获取Bean实例,这样就解决了循环依赖的问题。

5.2 总结: 58

Spring只能解决setter方法注入的单例bean之间的循环依赖。ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

标签:依赖,name,Wife,Bean,循环,public,Husband
From: https://blog.51cto.com/u_15784725/6423929

相关文章

  • 逍遥自在学C语言 | for循环详解
    前言C语言中的循环结构时,for循环是最常用的一种。它允许重复执行一段代码,直到满足特定条件为止。本文将详细介绍for循环的用法,并提供相关的可编译运行的C代码示例。一、人物简介第一位闪亮登场,有请今后会一直教我们C语言的老师——自在。第二位上场的是和我们一起学习......
  • Vue基础之表单控制 ,v-model进阶,箭头函数,JS循环
    目录一、表单控制1.checkbox选中2.radio单选3、checkbox多选4.购物车案例-结算二、v-model进阶三、箭头函数es6的语法1无参数,无返回值2有一个参数,没有返回值,可以省略括号3多个参数,不能省略括号4多个参数,不能省略括号,一个返回值5一个参数,一个返回值四、补充:JS循环一、表......
  • Java开发手册中为什么不建议在for循环中使用"+"进行字符串操作
    场景java开发手册中对于循环体中进行字符串的拼接要求如下:【推荐】循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。说明:下例中,反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回Stri......
  • 记录一下这次关于死循环使用愚蠢的行为
    在一个多线程的使用场景下,有个变量标记线程是否退出,然后我有这么一行代码while(!stopRequest){}这个问题是cpu某个核会一直占用,正确做法是在loop中sleep一段时间,例如1毫秒,10毫秒,100毫秒。让Cpu资源释放出去,sleep的时间越短,cpu资源就越紧张......
  • 二分查找的循环不变量全面解析
    二分查找的循环不变量全面解析原理二分查找的bug模版二分法变种寻找左侧/右侧元素的二分查找查找大于key的最小值upper查找小于key的最大值lower大于等于key的最小索引lower_ceil实践69.x的平方根215.数组中的第K个最大元素704.二分查找875.爱吃香蕉的珂珂1011.在......
  • Spring Bean生命周期详解
    本文结合Spring源码5.1.7.RELEASE,详细分析SpringBean生命周期,包括主要流程以及Spring一系列的扩展方法,最后通过测试实例演示主要步骤。Spring提供的Bean扩展方法大致分为三类,一类是BeanPostProcessor接口,一类是BeanFactoryProcessor接口,还有一类是Aware接口。Sprin......
  • 【Exception】The dependencies of some of the beans in the application context fo
    案发现场***************************APPLICATIONFAILEDTOSTART***************************Description:Thedependenciesofsomeofthebeansintheapplicationcontextformacycle:┌─────┐|asyncConfigdefinedinfile[E:\code\spring-boot-demo\t......
  • mybatis-plus扩展extend批量操作(自带批量操作是循环单条插入,效率太低)
    目录添加依赖构建三个配置-推荐放一个包里面让原本继承BaseMapper<实体>的Dao层改为继承EasyBaseMapper<实体>service层已经可以使用批量操作了 添加依赖<!--mybatis-plus组件--><dependency><groupId>com.baomidou</groupId><artifactId>myba......
  • Box/Spout 循环导出
    <?php$tmpFile=tmpfile();#创建临时文件$meta=stream_get_meta_data($tmpFile);$writer=ExcelWriter::newWriter($meta['uri']);#等于最下方类$writer->addHeader(['unionid','真实姓名','手机号'],11);//......
  • Flutter依赖注入
    依赖注入依赖注入(DependencyInjection,简称DI)是一种软件设计模式,它的主要目的是将对象之间的依赖关系解耦,使得代码更加可维护、可测试、可扩展,使得代码更易于维护和测试。在Flutter中,DI可以帮助我们管理应用程序中的各种依赖关系,包括服务、数据存储和UI组件等。在DI模式中,我们将......