22--Spring创建Bean的过程(四),解决循环依赖

版权声明:如有转载,请标明出处,谢谢合作! https://blog.csdn.net/lyc_liyanchao/article/details/82696236

什么是循环依赖?当两个或多个bean出现相互引用且引用形成一个闭环的时候,就是循环依赖。例如ClassA中有ClassB,ClassB中有ClassC,ClassC中有ClassA,那么A->B->C-A就形成了一个闭环,在Spring中一共有三种循环依赖,构造器循环依赖Setter循环依赖和Prototype作用域的循环依赖,对于这三种循环依赖,Spring并不是都解决的,通过一个表格来描述:


名称 是否可解决循环依赖
构造器循环依赖
Setter循环依赖
Prototype作用域的循环依赖

Spring对于循环依赖的解决还是比较复杂的,在16–Spring创建Bean的准备工作(一),从缓存中获取单例bean我们提到过几个变量,其中有这样一个,

/** Cache of singleton objects: bean name to bean instance. */
/** 缓存beanName和bean实例 key-->beanName,value-->beanInstance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
/** 缓存beanName和beanFactory key-->beanName,value-->beanFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
/** 缓存beanName和bean实例 key-->beanName,value-->beanInstance 该缓存主要为了解决bean的循环依赖引用 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Set of registered singletons, containing the bean names in registration order. */
/** 缓存所有注册的单例beanName */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

singletonFactories和earlySingletonObjects都是是缓存的beanName和bean实例之间的键值对,但是对于后者来说,当一个bean还在创建过程中的时候,我们就已经可以通过getBean方法去获取该bean的实例,这里大家一定有疑问,既然某个bean还在创建之中,为什么可以获取其实例呢?在前两个小节我们讲的是SpringBean的实例化,只是通过CGLIB或者反射创建了bean的实例,不代表整个bean的创建工作已经完成,Spring在bean的创建过程中会判断是否允许循环依赖,如果允许的话,那么不等该bean创建完成,就会将创建的bean实例缓存到earlySingletonObjects中(这里要将创建bean实例和创建完整的bean区分开来)。

我们还是通过一个例子来说明一下:

  • 新建bean
package com.lyc.cn.day11;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:36
 */
public interface Animal {
    void sayHello();
}
package com.lyc.cn.day11;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:36
 */
public class Cat implements Animal {
    /** 名称 **/
    private String name;

    /** 年龄 **/
    private int age;

    private Dog dog;

    @Override
    public void sayHello() {
        System.out.println("\n大家好,我是一只名叫" + getName() + "的猫,我跟" + dog.getName() + "之间有循环依赖关系!");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }
}
package com.lyc.cn.day11;

/**
 * @author: LiYanChao
 * @create: 2018-09-13 17:41
 */
public class Dog implements Animal {

    /** 名称 **/
    private String name;

    /** 年龄 **/
    private int age;

    private Cat cat;

    @Override
    public void sayHello() {
        System.out.println("\n大家好,我是一只名叫" + getName() + "的狗,我跟" + cat.getName() + "之间有循环依赖关系!");
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }
}

一个接口和两个实现类,其中Cat类引用了Dog类,Dog类引用了Cat类,这样就形成一个闭环,就是所谓的循环引用。

  • 新建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- cat bean 依赖dog-->
    <bean id="cat" class="com.lyc.cn.day11.Cat">
        <property name="name" value="美美"/>
        <property name="age" value="3"/>
        <property name="dog" ref="dog"/>
    </bean>

    <!--dog bean 依赖cat-->
    <bean id="dog" class="com.lyc.cn.day11.Dog">
        <property name="name" value="壮壮"/>
        <property name="age" value="5"/>
        <property name="cat" ref="cat"/>
    </bean>

</beans>

Spring是不能解决构造器循环依赖的,这里我们也不做过多的演示,只演示Setter和Prototype两种即可

  • 新建测试类
package com.lyc.cn.day11;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;

/**
 * @author: LiYanChao
 * @create: 2018-09-07 16:43
 */
public class MyTest {
    private XmlBeanFactory xmlBeanFactory;

    @Before
    public void initXmlBeanFactory() {
        System.out.println("========测试方法开始=======\n");
        xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("day11.xml"));
    }

    @After
    public void after() {
        System.out.println("\n========测试方法结束=======");
    }

    @Test
    public void test() {
        Cat cat = xmlBeanFactory.getBean("cat", Cat.class);
        cat.sayHello();

        Dog dog = xmlBeanFactory.getBean("dog", Dog.class);
        dog.sayHello();
    }


}
  • 运行测试用例
========测试方法开始=======


大家好,我是一只名叫美美的猫,我跟壮壮之间有循环依赖关系!

大家好,我是一只名叫壮壮的狗,我跟美美之间有循环依赖关系!

========测试方法结束=======

可以看到,对于单例bean之间的循环引用,通过Setter方法注入,是可以解决的,我们可以将bean的作用域改为Prototype,再来看一下运行结果。

========测试方法开始=======


========测试方法结束=======
// 异常信息太多了,就粘贴部分
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cat': Requested bean is currently in creation: Is there an unresolvable circular reference?
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:256)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
    at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
    ... 40 more

当我们获取bean时,抛出了循环依赖的异常,Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cat': Requested bean is currently in creation: Is there an unresolvable circular reference?,其原因就是Spring不会缓存Prototype作用域的bean的实例,那么自然也就不会缓存到earlySingletonObjects中,也就无法解析循环依赖了。

这里我们一定要将创建一个完整的bean和创建一个bean的实例区分开来:

  • 创建一个完整的bean:是指在bean的生命周期里,这个bean已经可以正常使用了,是我们通过getBean方法返回而得到的bean。
  • 创建一个bean的实例:是指bean类被实例化,例如Cat类,通过反射创建了cat的实例。但是创建了bean的实例之后,并不代表这个bean已经就可以被正常使用了,Spring对bean的实例还有很多后续操作,例如应用后处理器,填充bean的属性等。但是如果这个bean被检测到可以循环依赖的话,那么在创建其实例完成时候,就会将其实例缓存到earlySingletonObjects对象中,提前暴露一个ObjectFactory对象,那么我们就可以通过ObjectFactory的getObject方法去获取bean的实例,从而解决循环依赖。

Setter循环依过程赖简要分析:

  • 假如ClassA依赖ClassB,ClassB依赖ClassA,那么这两个类之间形成了一个循环依赖
  • 当创建ClassA时,首先通过其无参构造方法创建bean的实例,提前暴露ObjectFactory,并将其实例放入到earlySingletonObjects(当前创建bean池)中。然后填充bean的属性,通过Setter方法注入ClassB。
  • 对于ClassB,其创建过程跟创建ClassA一样,但是当其通过Setter方法去注入ClassA时,因为ClassA的实例已经提前暴露,所以可以通过getObject方法去获取ClassA的实例,从而解决了循环依赖

以上的内容纯属个人理解,如有不对的地方,欢迎指正!对于这一部分内容的源码分析,我们会在后面的章节中讲解。

猜你喜欢

转载自blog.csdn.net/lyc_liyanchao/article/details/82696236