系列二十、Spring循环依赖问题

本文探讨了Spring框架中循环依赖的概念,通过构造方法和set方法注入的实例展示其区别,并介绍了Spring如何利用三级缓存解决单例bean的循环依赖问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

        循环依赖是指多个bean之间相互依赖,形成了一个闭环。比如A依赖于B、B依赖于C、C依赖于A,形成了一个圈,如:

二、循环依赖案例

2.1、构造方法注入产生循环依赖案例

2.1.1、ServiceA

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:14
 * @Description:
 */
@Service
public class ServiceA {

    private ServiceB serviceB;

    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

2.1.2、ServiceB

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:14
 * @Description:
 */
@Service
public class ServiceB {

    private ServiceA serviceA;

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

2.1.3、MySpringConfig

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/23 15:29
 * @Description:
 */
@Configuration
@ComponentScan(basePackages = {"org.star"})
public class MySpringConfig {

}

2.1.4、AopFullAnnotationMainApp

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/23 15:14
 * @Description:
 */
@Slf4j
public class AopFullAnnotationMainApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
        ServiceA serviceA = context.getBean("serviceA", ServiceA.class);
        log.info("serviceA:{}", serviceA);
    }
}

2.1.5、结论

使用构造方法注入属性会产生循环依赖问题。

2.2、set方法注入不会产生循环依赖案例

2.2.1、ServiceAA

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:27
 * @Description:
 */
@Service
public class ServiceAA {

    @Resource
    private ServiceBB serviceBB;

}

2.2.2、ServiceBB

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/27 9:27
 * @Description:
 */
@Service
public class ServiceBB {

    @Resource
    private ServiceAA serviceAA;

}

2.2.3、MySpringConfig(同上)

2.2.4、AopFullAnnotationMainApp

/**
 * @Author : 一叶浮萍归大海
 * @Date: 2023/11/23 15:14
 * @Description:
 */
@Slf4j
public class AopFullAnnotationMainApp {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
        ServiceAA serviceAA = context.getBean(ServiceAA.class);
        log.info("serviceAA:{}", serviceAA);
    }
}

2.2.5、结论

使用set方法注入属性不会产生循环依赖问题。

2.2.6、注意事项

        默认情况下使用set方法注入属性可以解决循环依赖问题,但是有一个前提,bean是单例的,当把bean设置为多例后,再使用set注入时依然会产生循环依赖问题。

三、Spring解决循环依赖的方法

3.1、三级缓存

        所谓Spring的三级缓存是指Spring内部解决循环依赖问题的三个Map,即 DefaultSingletonBeanRegistry 中定义的三个Map。部分源码如下:

3.2、A、B对象在三级缓存中的迁移过程

第一步:A创建过程中需要B,于是A将自己放到三级缓存里面,去实例化B;

第二步:B实例化的时候发现需要A,于是B先查找一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,于是找到A,然后把三级缓存里面的这个A放到二级缓存里面,并删除三级缓存里面的A;

第三步:B顺利完成初始化,并将自己放到一级缓存里面(注意:此时B里面的A依然是创建中的状态),然后接着回来创建A,此时B已经创建结束,直接从一级缓存里面拿到B,然后完成创建,并将A自己放到一级缓存里面。

3.3、断点顺序

# 参考B站尚硅谷周阳老师录制的视频,链接地址如下
 
https://www.bilibili.com/video/BV1Hy4y1B78T?p=39&spm_id_from=pageDriver&vd_source=72ebc194dbfa51ec950a52c25c337f7c

3.4、解决循环依赖的步骤

        第一步:Spring创建bean主要分为两个步骤,即:创建原始bean对象 & 填充对象属性和初始化;

        第二步:每次创建bean之前,Spring都会从缓存中查询该bean是否存在,如果缓存中存在,则直接拿来用,如果没有Spring则去创建原始对象并放入三级缓存中,因为是单例,只能有一个;

        第三步:当创建完A的原始对象后(此时A已经在三级缓存中了),接下来为A填充属性,这时候发现A依赖B,接着又去创建B,同样的流程,创建完B的原始对象后,填充属性时发现B又依赖于A,B实例化的时候需要A,于是B先从一级缓存里面查找A,没有找到,接着再查二级缓存,还是没有,再查三级缓存,于是找到了A,然后Spring把三级缓存里面的A放到二级缓存里面,并删除三级缓存里面的A,B顺利完成初始化,并将自己放入到一级缓存里面(注意:此时B里面的属性A依然是创建中的状态),然后接着回来继续为A填充属性,此时B已经创建结束,Spring直接从一级缓存中取出B,为A赋值,完成A的初始化,然后把A自己放入到一级缓存里面,并删除二级缓存里面的A,此时A,B各自完成实例化。

3.5、解决循环依赖的思想

        Spring解决循环依赖靠的是bean的 中间态 这个概念,而这个中间态指的是 已经实例化但是还没有初始化的状态,即:半成品;实例化的过程又是通过构造器完成的,如果A还没有创建出来怎么提前曝光?这也就解释了使用构造方法注入属性为什么无法解决循环依赖的问题。

Spring解决单例bean循环依赖的三个Map(三级缓存):

        # 一级缓存:单例池,存放已经经历了完整生命周期的bean

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

        # 二级缓存:存放早期暴露出来的bean对象,此时bean的生命周期尚未结束(属性还未填充完毕)

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

        # 三级缓存:存放可以生成bean的工厂

        private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

3.6、详细过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值