Spring中的循环依赖问题

Spring 循环依赖

一、循环依赖是什么?

循环依赖是指,类与类之间的依赖关系形成闭环,就会导致循环依赖问题的产生。如,A类依赖B类,B类依赖C类,C类又依赖A类,这样就形成了循环依赖问题。

Spring中的循环依赖问题

二、Spring中解决循环依赖的方式

前提,都是单例模式创建Spring Bean对象

1、构造注入(报错)

初始工作:准备三个类,建构造方法。形成循环依赖

public class ClassA {
    private ClassB b;
    public ClassA() {
    }
    public ClassA(ClassB b) {
        this.b = b;
    }
}

public class ClassB {
    private ClassC c;
    public ClassB() {
    }
    public ClassB(ClassC c) {
        this.c = c;
    }
}

public class ClassC {
    private ClassA a;
    public ClassC() {
    }
    public ClassC(ClassA a) {
        this.a = a;
    }
}

在配置文件中,以构造注入的方式注入依赖

<!--声明A-->
<bean id="a" class="com.ape.xhyl.ClassA">
    <!--构造注入b依赖-->
    <constructor-arg name="b" ref="b"></constructor-arg>
</bean>
<!--声明B-->
<bean id="b" class="com.ape.xhyl.ClassB">
    <!--构造注入c依赖-->
    <constructor-arg name="c" ref="c"></constructor-arg>
</bean>
<!--声明C-->
<bean id="c" class="com.ape.xhyl.ClassC">
    <!--构造注入a依赖-->
    <constructor-arg name="a" ref="a"></constructor-arg>
</bean>

测试类

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

报错

org.springframework.beans.factory.BeanCurrentlyInCreationException

spring的构造注入未解决循环依赖问题

2、Setter注入(解决循环依赖)

初始工作:准备三个类,建立set方法。形成循环依赖

public class ClassA {
    private ClassB b;
    public ClassA() {
    }
    public void setB(ClassB b) {
        this.b = b;
    }
}

public class ClassB {
    private ClassC c;
    public ClassB() {
    }
    public void setC(ClassC c) {
        this.c = c;
    }
}

public class ClassC {
    private ClassA a;
    public ClassC() {
    }
    public void setA(ClassA a) {
        this.a = a;
    }
}

在配置文件中,以Set注入的方式注入依赖

<!--声明A-->
<bean id="a" class="com.ape.xhyl.ClassA">
    <!--set注入b依赖-->
    <property name="b" ref="b"></property>
</bean>
<!--声明B-->
<bean id="b" class="com.ape.xhyl.ClassB">
    <!--set注入c依赖-->
    <property name="c" ref="c"></property>
</bean>
<!--声明C-->
<bean id="c" class="com.ape.xhyl.ClassC">
    <!--set注入a依赖-->
    <property name="a" ref="a"></property>
</bean>

测试类

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

结果

com.ape.xhyl.ClassA@3632be31

成功获取classA,说明set注入解决了循环依赖问题。

3、属性注入(解决循环依赖)

初始工作:准备三个类,通过注解属性注入依赖。形成循环依赖

@Component
public class ClassA {
    @Autowired
    private ClassB b;
}

@Component
public class ClassB {
    @Autowired
    private ClassC c;
}

@Component
public class ClassC {
    @Autowired
    private ClassA a;
}

在配置文件中,扫描每个包的类

<context:component-scan base-package="com.ape"></context:component-scan>

测试类

public class Test01 {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        ClassA a = (ClassA) applicationContext.getBean("classA");
        System.out.println(a);
    }
}

结果

com.ape.xhyl.ClassA@679b62af

获取到了classA类,解决了循环依赖问题

三、解决循环依赖的原理

在解决循环依赖问题之前,需要先了解Spring Bean的生命周期:

实例化 -> 属性赋值 -> 初始化 -> 销毁

从这里可以大概的看的出来,为什么构造注入的方式会失败,而set注入和属性注入的方式能解决循环依赖。那么Spring在处理循环依赖的地方,就是在属性赋值的阶段。

因为实例化就是调用构造方法,所以在构造方法中注入循环的依赖,就会报BeanCurrentlyInCreationException的错误,当前Bean异常创建。

所以这里我们主要查看在属性赋值阶段Spring如何解决循环依赖的问题。

首先,Spring内部维护了三个Map,也就是我们通常说的三级缓存。

源码:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
}

我们可以看到在DefaultSingletonBeanRegistry中有三个map集合,

singletonObjects(一级缓存)就是完整的AOP容器了,缓存创建完成单例Bean的地方。
earlySingletonObjects(二级缓存)映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为“Bean”,只是一个Instance。
singletonFactories(三级缓存)映射创建Bean的原始工厂。

Spring是如何通过这三级缓存来解决循环依赖问题呢?

简单概述流程

实例化A,A还没有完成属性赋值和初始化时,A只是一个半成品Bean对象

将A放进三级缓存中

在A属性赋值阶段,发现A需要依赖B,但此时B还未实例化(一、二、三级缓存中均未发现B)

暂缓A的属性赋值,先实例化B

此时B也为完成属性赋值和初始化,B也是一个半成品

将B也放进三级缓存中

在B属性赋值阶段,发现B又需要依赖C,但C也没有实例化(一、二、三级缓存中均未发现C)

暂缓B的属性赋值,实例化C

将C放进三级缓存中

属性赋值C时发现需要依赖A,而A在三级缓存中有,所以直接从三级缓存中引用A,并将A放进二级缓存,同时删除三级缓存中的A

此时C完成属性赋值,继续完成C的初始化操作,所以将C放入一级缓存中(此时C已经是成品Bean对象)并删除三级缓存中的C。

开始回溯,回溯到B的属性赋值阶段,发现C在一级缓存中,在B中注入C

完成B的属性赋值和初始化,将B放入一级缓存(同时删除三级缓存中的B)

回到A的属性赋值阶段,发现B在一级缓存中,直接引用B。

完成A的属性赋值,和初始化后,将A放入一级缓存(删除二级缓存中的A)

至此,ABC都放入一级缓存,等待使用

源码:

创建Bean的方法:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
    BeanWrapper instanceWrapper = null;
	
    if (instanceWrapper == null) {
        // ① 实例化对象
        instanceWrapper = this.createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
    Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
   
    // ② 判断是否允许提前暴露对象,如果允许,则直接添加一个 ObjectFactory 到三级缓存
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // 添加三级缓存的方法详情在下方
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // ③ 填充属性
    this.populateBean(beanName, mbd, instanceWrapper);
    // ④ 执行初始化方法,并创建代理
    exposedObject = initializeBean(beanName, exposedObject, mbd);
   
    return exposedObject;
}

添加三级缓存的方法:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) { // 判断一级缓存中不存在此对象
            this.singletonFactories.put(beanName, singletonFactory); // 添加至三级缓存
            this.earlySingletonObjects.remove(beanName); // 确保二级缓存没有此对象
            this.registeredSingletons.add(beanName);
        }
    }
}
@FunctionalInterface
public interface ObjectFactory<T> {
	T getObject() throws BeansException;
}

Spring 在实例化对象的之后,就会为其创建一个 Bean 工厂,并将此工厂加入到三级缓存中。

如何获取缓存中的依赖对象呢?

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 一级缓存
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 二级缓存
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 三级缓存
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // Bean 工厂中获取 Bean
                    singletonObject = singletonFactory.getObject();
                    // 放入到二级缓存中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

当 Spring 为某个 Bean 填充属性的时候,它首先会寻找需要注入对象的名称,然后依次执行 getSingleton() 方法得到所需注入的对象,而获取对象的过程就是先从一级缓存中获取,一级缓存中没有就从二级缓存中获取,二级缓存中没有就从三级缓存中获取,如果三级缓存中也没有,那么就会去执行 doCreateBean() 方法创建这个 Bean。

四、总结

解决循环依赖问题流程图

Spring使用三级缓存解决循环依赖的流程图

set注入和属性注入解决循环依赖的方式

1、调用构造方法

2、放到三级缓存

3、属性赋值。

源码中,创建Bean实例对象的核心方法实现:

// bean对象实例创建的核心实现方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
    throws BeanCreationException {
    // 省略其他代码
    // 1. 调用构造函数创建该bean对象,若不存在构造函数注入,顺利通过
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    // 2. 在singletonFactories缓存中,放入该bean对象,以便解决循环依赖问题
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    // 3. populateBean方法:bean对象的属性赋值
    populateBean(beanName, mbd, instanceWrapper); 		
    // 省略其他代码
    return exposedObject;
}

构造注入为什么无法解决循环依赖

从这里就可以看出,为什么构造注入无法解决循环依赖的问题:

在①中,创建A对象,发现构造方法中需要依赖B对象,

此时A无法执行存放三级缓存中,就需要先创建B对象,

B对象同理,在构造方法中又调用C的构造方法,

A未在任何缓存中所以又执行A的构造方法,形成死循环,所以报错

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值