Spring 如何解决循环依赖?

简介: Spring 如何解决循环依赖?


什么是循环依赖

一个或多个对象之间存在直接或间接的依赖关系,这种依赖关系构成一个环形调用,有下面 3 种方式。

我们看一个简单的 Demo,对标“情况 2”。

@Service
public class Louzai1 {
    @Autowired
    private Louzai2 louzai2;
    public void test1() {
    }
}
@Service
public class Louzai2 {
    @Autowired
    private Louzai1 louzai1;
    public void test2() {
    }
}

这是一个经典的循环依赖,它能正常运行,后面我们会通过源码的角度,解读整体的执行流程。

三级缓存

解读源码流程之前,spring 内部的三级缓存逻辑必须了解,要不然后面看代码会蒙圈。

第一级缓存:singletonObjects,用于保存实例化、注入、初始化完成的 bean 实例;

二级缓存:earlySingletonObjects,用于保存实例化完成的 bean 实例;

第三级缓存:singletonFactories,用于保存 bean 创建工厂,以便后面有机会创建代理对象。

这是最核心,我们直接上源码:

执行逻辑:

先从“第一级缓存”找对象,有就返回,没有就找“二级缓存”;

找“二级缓存”,有就返回,没有就找“三级缓存”;

找“三级缓存”,找到了,就获取对象,放到“二级缓存”,从“三级缓存”移除。

原理执行流程

我把“情况 2”执行的流程分解为下面 3 步,是不是和“套娃”很像 ?

整个执行逻辑如下:

1、在第一层中,先去获取 A 的 Bean,发现没有就准备去创建一个,然后将 A 的代理工厂放入“三级缓存”(这个 A 其实是一个半成品,还没有对里面的属性进行注入),但是 A 依赖 B 的创建,就必须先去创建 B;

2、在第二层中,准备创建 B,发现 B 又依赖 A,需要先去创建 A;

3、在第三层中,去创建 A,因为第一层已经创建了 A 的代理工厂,直接从“三级缓存”中拿到 A 的代理工厂,获取 A 的代理对象,放入“二级缓存”,并清除“三级缓存”;

4、回到第二层,现在有了 A 的代理对象,对 A 的依赖完美解决(这里的 A 仍然是个半成品),B 初始化成功;

5、回到第一层,现在 B 初始化成功,完成 A 对象的属性注入,然后再填充 A 的其它属性,以及 A 的其它步骤(包括 AOP),完成对 A 完整的初始化功能(这里的 A 才是完整的 Bean)。

6、将 A 放入“一级缓存”。

Spring Bean的生命周期

getBean(a)–>实例化A–>属性注入–>初始化A–>销毁

三级缓存的作用:

singletonObjects(一级缓存):存放实例化–>代理–>属性注入–>初始化后的对象

earlySingletonObjects(二级缓存):存放实例化–>代理–>属性注入后的对象

singletonFactories(三级缓存):存放对象工厂,可以从对象工厂中拿到还未属性注入的对象(对象工厂便于创建代理对象)

当未添加三级缓存时候:

当加入三级缓存后:


目录
相关文章
|
1月前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
29天前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
16天前
|
Java Spring 容器
循环依赖难破解?Spring Boot神秘武器@RequiredArgsConstructor与@Lazy大显神通!
【8月更文挑战第29天】在Spring Boot应用中,循环依赖是一个常见问题。当两个或多个Bean相互依赖形成闭环时,Spring容器会陷入死循环。本文通过对比@RequiredArgsConstructor和@Lazy注解,探讨它们如何解决循环依赖问题。**@RequiredArgsConstructor**:通过Lombok生成包含final字段的构造函数,优先通过构造函数注入依赖,简化代码但可能导致构造函数复杂。**@Lazy**:延迟Bean的初始化,直到首次使用,打破创建顺序依赖,增加灵活性但可能影响性能。根据具体场景选择合适方案可有效解决循环依赖问题。
26 0
|
2月前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
30天前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
2月前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
2月前
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
|
2月前
|
存储 缓存 Java
Spring循环依赖问题之循环依赖异常如何解决
Spring循环依赖问题之循环依赖异常如何解决
|
存储 缓存 Java
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?
在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。
447 0
|
4月前
|
存储 缓存 Java
【Spring系列笔记】依赖注入,循环依赖以及三级缓存
依赖注入: 是指通过外部配置,将依赖关系注入到对象中。依赖注入有四种主要方式:构造器注入、setter方法注入、接口注入以及注解注入。其中注解注入在开发中最为常见,因为其使用便捷以及可维护性强;构造器注入为官方推荐,可注入不可变对象以及解决循环依赖问题。本文基于依赖注入方式引出循环依赖以及三层缓存的底层原理,以及代码的实现方式。
69 0