06-解决Spirng中的循环依赖问题

Bean的循环依赖问题

循环依赖: A对象中有B属性 , B对象中有A属性(丈夫类Husband中有Wife的引用, 妻子类Wife中有Husband的引用)

06-解决Spirng中的循环依赖问题_第1张图片

toString()方法重写时直接输出wife/husband会出现递归导致的栈内存溢出错误

  • 直接输出wife/husband会调用它们的toString()方法, 在toString()方法又会调用husband/wife的toString()方法一直循环
//丈夫类
public class Husband {
    private String name;
    private Wife wife;
   	// 属性的setter方法

    // toString()方法重写时直接输出wife会出现递归导致的栈内存溢出错误,需要输出wife.getName()
    @Override
    public String toString() {
        return "Husband{" +
                "name='" + name + '\'' +
                ", wife=" + wife.getName() +
                '}';
    }
}
//妻子类
public class Wife {    
    private String name;
    private Husband husband;
    //属性的setter方法

    // toString()方法重写时不能直接输出husband,需要输出husband.getName()
    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                ", husband=" + husband.getName() +
                '}';
    }
}

singleton下的set注入

Spring可以解决在singleton+setter模式下出现的循环依赖问题, 因为在这种模式下Spring对Bean的管理主要分为清晰的两个阶段

  • 实例化对象阶段:Spring容器实例化Bean的时候会先创建对象 , 不等其属性赋值就会曝光加入正在创建的bean的缓存中 , 这样其他bean就可以直接引用它
  • 对象的属性赋值阶段Bean曝光之后,再调用set方法进行属性的赋值
  • 注意: 只有在scope是singleton的情况下,Bean才会采取提前曝光的措施 , 多实例下不会曝光

<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">
    
    <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>
beans> 
public class CircularDependencyTest {
    @Test
    public void testSingletonAndSet(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);
        Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);
        //Husband{name="张三",wife=小花}
        System.out.println(husbandBean);
        //Wife{name="小花",husband=张三}
        System.out.println(wifeBean);
    }
}

prototype下的set注入

若循环依赖的所有Bean的scope="prototype"时 , Spring无法解决它们产生的循环依赖问题,此时会出现BeanCurrentlyInCreationException异常

  • 当两个bean的scope都是prototype的时候才会出现Bean正在处于创建中异常 , 如果其中任意一个是singleton的就不会出现异常

<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">
    
    
    <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>
    
     
    <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="singleton">
        <property name="name" value="小花"/>
        <property name="husband" ref="husbandBean"/>
    bean>
beans>

singleton下的构造注入

Spring无法解决singleton下的构造注入产生的循环依赖,会出现BeanCurrentlyInCreationException异常

  • 因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开导致循环依赖, 想要实例化对象必须完成对象属性的赋值
//丈夫类
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 +
                '}';
    }
}
//妻子类
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 +
                '}';
    }
}

<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">
    <bean id="hBean" class="com.powernode.spring6.bean2.Husband" scope="singleton">
        <constructor-arg name="name" value="张三"/>
        <constructor-arg name="wife" ref="wBean"/>
    bean>

    <bean id="wBean" class="com.powernode.spring6.bean2.Wife" scope="singleton">
        <constructor-arg name="name" value="小花"/>
        <constructor-arg name="husband" ref="hBean"/>
    bean>
beans>

Spring解决循环依赖的原理

Spring解决set + singleton模式下循环依赖的问题是将实例化Bean给Bean属性赋值这两个动作分开去完成(这两步不要求在同一个时间点上完成)

  • 先实例化单实例的Bean: 通过调用无参数构造方法时把所有的单例Bean实例化出来,放到一个Map集合(缓存) 当中曝光给外界
  • 然后给Bean属性赋值:调用setter方法来完成对象的属性赋值

Spring框架底层源码级别的缓存实现

  • 完整单例对象的缓存:key存储bean名称,value存储的单例Bean对象属性都已经赋值【一级缓存】
  • 早期单例对象的缓存:key存储bean名称,value存储早期的Bean对象属性没有赋值【二级缓存】
  • 单例工厂对象的缓存:key存储bean名称,value存储每个Bean对象对应的ObjectFactory单例工厂对象【三级缓存】

06-解决Spirng中的循环依赖问题_第2张图片

AbstractAutowireCapableBeanFactory类的doCreateBean()方法完成Bean的创建和属性赋值

  • creatBeanInstance只会创建Bean对象, addSingletonFactory方法中将bean对象缓存起来然后曝光, populateBean方法填充bean即给bean的属性赋值

  • DefaultSingletonBeanRegistry的addSingletonFactory()方法的作用是将创建Bean对象的ObjectFactory工厂对象提前曝光

06-解决Spirng中的循环依赖问题_第3张图片

spring会先从一级缓存中获取Bean,获取不到则从二级缓存中获取Bean,如果还是获取不到则从三级缓存中获取之前曝光的ObjectFactory对象,然后通过ObjectFactory对象获取Bean实例并放到二级缓存

06-解决Spirng中的循环依赖问题_第4张图片

你可能感兴趣的:(Bean,spring,java,循环依赖)