Spring 循环依赖以及三级缓存

1、什么是循环依赖


// 假设有类AService,其中有成员属性Bservice
@Component
class AService{

    @Autowired
    private Bservice bservice;
       
    .....
}
// 假设有类BService,其中有成员属性Aservice
@Component
class BService{

    @Autowired
    private Aservice aservice;
       
    .....
}

Spring在创建AService这个bean时,会发现它依赖于BService这个bean;随后Spring会去创建BService这个bean,但是此时Spring会发现BService这个bean,依赖于AService这个bean,随后她又会去创建AService这个bean,以此循环反复。因此,要解决这个问题,我们首先需要知道Bean的创建过程。

2、Bean的创建过程

Bean创建过程会走以下几个步骤:

bean的实例化  -> 属性填充(依赖注入) -> 初始化操作(动态代理创建之类)-> 加入到单例池中

因此,从这个步骤,我们可以初步确定,Bean循环依赖会发生在“属性填充”这个过程。以上面代码为例:

1、Aservice实例化

2、Bservice属性填充

        2.1 Bservice存于单例池中

                2.1.1 取出Bservice的bean

                2.1.1 返回

        2.2 Bervice不存于单例池中,那么创建Bservice的bean

                2.2.1 Bservice实例化

                2.2.2 属性填充(这里发现依赖Aservice的bean,但是单例池中并没有Aservice的bean,会触发Aservice的bean创建。进而引起循环)

                2.2.3 初始化操作

                2.2.4 加入单例池

3、其他属性的填充

4、初始化

5、放入单例池中

3、循环依赖的解决思路

1、首先需要能判定循环依赖的出现。Spring的做法是在开始创建一个单例 Bean 时,会将该 Bean 的名称添加到creatingSet集合中,即

private final Set<String> singletonsCurrentlyInCreation

当Bservice的bean被创建时,发现存在Aservice的依赖,那么会先去creatingSet判断Aservice是否处于正在创建的情况,如果是,那么就可能出现循环依赖。

2、打破循环依赖。打破循环依赖的直观做法就是给循环链添加一个出口。而Spring的做法是:Bean在创建时,若发现依赖某个bean,且这个bean不存于单例池中以及这个bean的名称存于creatingSet,那么提前对所依赖的bean进行初始化。以上面的过程为例子:

0、bean的名称存于creatingSet中

1、Aservice实例化

2、Bservice属性填充

        2.1 Bservice存于单例池中

                2.1.1 取出Bservice的bean

                2.1.1 返回

        2.2 Bervice不存于单例池中,那么创建Bservice的bean

                2.2.1 Bservice实例化

                2.2.2 属性填充:判断Aservice的bean是否存于单例池且 bean的名称是否存于creatingSet中 -> 若是,则对Aservice的bean进行提前初始化(例如动态代理对象创建)。

                2.2.3 初始化操作

                2.2.4 加入单例池

3、其他属性的填充

4、初始化(需要判断是否已经进行了提前初始化,如果是的话,那么不需要再进行初始化)

5、放入单例池中

然而,上述过程还有问题:

1、提前初始化需要使用到相应的bean对象,例如创建动态代理需要有目标类,应该去哪里找?

2、假设Aservice的属性注入不止有Bservice属性,可能还有Cservice属性,并且Cservice也依赖于Aservice。那么在Aservice的bean创建过程中,除了Bservice属性的依赖注入,可能还有Cservice属性的依赖注入。那么在创建Cservice的bean时,Cservice的Aservice属性字段也应该依赖Aservice的bean,即也需要对Aservice的bean进行提前初始化。此时就出现Bservice的bean和Cservice的bean所依赖的Aservice的提前初始化bean不是同一个bean,不满足单例。

4、三级缓存机制

首先,一级缓存 就是单例池,用于存放完全初始化好的单例 Bean。他是一个Map,key为bean的名称,object对应bean。所以这也可以知道为什么bean的名称不能相同了。对应源码:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

其次,当出现循环依赖时,需要使用Aservice的bean进行提前初始化,就需要对Aservice的实例化(注意不是初始化,要注意区分)后的bean保存起来,以便后续在Bservice的bean的属性注入阶段进行提前初始化。但是,源码里面,它并不是将Aservice的实例化对象直接保存的,而是保存Aservice的bean初始化对应的lambda表达式。这样获取某个bean时,就可以直接根据lambda表达式得到它初始化后的bean,而不是仅实例化后的bean。这就是三级缓存。对应相关源码:

// 这个是用于保存bean所对应的初始化lambda表达式,也就是三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);

// ObjectFactory<?>就是函数式接口
@FunctionalInterface
public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

// 在创建bean的时候,都会将这个bean名称,以及初始化操作保存到singletonFactories 中
this.addSingletonFactory(beanName, () -> this.getEarlyBeanReference(beanName, mbd, bean));

// 这个是初始化操作,对当前bean进行不断的后处理,即初始化过程
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && this.hasInstantiationAwareBeanPostProcessors()) {
        for(SmartInstantiationAwareBeanPostProcessor bp : 
this.getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }

    return exposedObject;
}

二级缓存,就是来存放提前初始化后的bean的。对应代码如下:

private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

固有:

// 一级缓存,即单例池;多线程下存在并发冲突,因此ConcurrentHashMap
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256); 
// 三级缓存;单线程写入,单线程读取;所以HashMap
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
// 二级缓存;多线程读写,因此是ConcurrentHashMap
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

5、通过构造方法进行依赖注入所出现的循环依赖问题


// 假设有类AService,其中有成员属性Bservice
@Component
class AService{

    private Bservice bservice;

    AService(Bservice bservice){
        this.bservice = bservice;
    }
       
    .....
}
// 假设有类BService,其中有成员属性Aservice
@Component
class BService{

    private Aservice aservice;

    BService(Aservice aservice){
        this.aservice= aservice;
    }
       
    .....
}

之所以三级缓存不能解决构造方法依赖注入的循环依赖问题,是因为AService和BService在实例化的时候就出现循环依赖了,连实例化都完成不了,更不可能依靠后续的三级缓存来解决循环依赖问题了。

解决思路:

        1、给上面代码加一个无参数的构造方法;虽然解决了循环依赖,但是没能依赖注入。

        2、使用@Lazy注解构造方法。这样在依赖注入的时候,spring会自动创建一个依赖的代理对象来代替依赖的bean;这样,后续AService对依赖的BService的bean的操作,都会打到BService的bean的代理类,由他找到bean容器中的BService的bean,再调用其对应的方法。

6、总结

一级缓存是单例池,用于存储最终的bean;二级缓存是提前曝露初始化后bean,以供后续属性赋值的依赖注入,保证单例;三级缓存提供了循环依赖的出口,提供提前初始化后的bean;此外,还需要借助creatingSet来保存那些bean正常创建,用于循环依赖的判断;earlyProxyReferences(文章没提到)可以用判断是否需要进行初始化。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值