面试问Spring循环依赖?今天通过代码调试让你记住

简介: 该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。

说明

看这篇文章的同学需要有对Spring ioc和di流程有了解,知道Spring bean创建和bean属性填充。

回忆从Spring容器获取bean

首先,我们可以找到Spring获取bean的方法,它会从三个缓存中获取。

    //一级缓存,存储可以直接使用的bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    //二级缓存 存储不完整对象 不能直接用的 循环依赖时,某些依赖的属性没有设置,这个时候bean就是不完整对象
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    //三级缓冲 存储bean工厂 可以延迟调用实例化 使用的时候才调用工厂方法获取对象
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    //从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) {
   
   
                    //三级缓存
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

这里为什么需要3个缓存呢?为啥2个不行?1个也不行?我们下面开始来看到底为什么?

模拟循环依赖场景

A,B类分别有对方的类属性

@Service
public class A {
   
   

    private B b;
}
@Service
public class B {
   
   

    private A a;
}

模拟循环依赖相互手动注入

a对象依赖b对象,b对象又依赖a对象,属性注入流程就是先实例化B,注入A,然后再填充A

A a = new A();

B b = new B();

b.setA(a); //B里面注入不完整的A了

a.setB(b); //注入依赖的对象,A里面有B了,a注入完成变成完整的a

调试Spring循环依赖注入处理流程

相同的场景,看看Spring如何处理。

第一步,创建A类的对象a

image.png 在Spring中,A类实例化后,缓存了一个工厂,而不是实例A,why?

第二步 开始注入a对象的属性b

image.png a注入b的的时候,缓存注册工厂类,而不是b实例

第三步 b对象又开始注入属性a对象

image.png b对象又开始注入a对象了,这个时候a只有对应的工厂。

通过A工厂返回了a的代理对象,将代理对象放入到提早暴露的缓存earlySingletonObjects(二级缓存),这个时候的a并没有将b注入完成,还是一个半成品,所以a还是先放入到一个中间缓存。 image.png

第四步 b对象注入a对象

image.png 这个时候B注入完成了,A还没注入完成。B里面的A里面的B没有赋值

第五步 注入完整B对象,(实际还不是完整的)

image.png

第6步 a对象的b属性注入

image.png 最后将A对象的B属性注入,这个时候A对象完整了,所以B对象也完整了(弥补上一步骤)

完整a对象图

image.png

完整b对象图

image.png

总结一下,为什么使用三级缓存?

下次面试再被问到循环依赖的问题,我会怎么来给面试官说明白呢?

1、Spring通过3个缓存map来解决循环依赖的问题,分别是singletonObjects,singletonFactories,earlySingletonObjects

2、singletonObjects这个缓存是存储完整的对象,可以直接使用的。

3、singletonFactories这个缓存是为了延迟初始化,是解决循环依赖的关键,存在循环依赖注入时,可能注入的对象需要被代理,这个工厂类来实例化一个代理类。

4、earlySingletonObjects这个缓存是为了在a注入b,而b又注入a这个阶段,a是一个半成品,需要用来存储a这种半成品bean的状态。

需要等待a将b注入之后,这个缓存就要移除。

5、singletonFactories和earlySingletonObjects都是存储对象的中间状态,依赖它们保证最终注入的对象是完整的,依赖注入完成后会被清除。

相关文章
|
3月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
365 3
|
2月前
|
人工智能 监控 Java
零代码改造 + 全链路追踪!Spring AI 最新可观测性详细解读
Spring AI Alibaba 通过集成 OpenTelemetry 实现可观测性,支持框架原生和无侵入探针两种方式。原生方案依赖 Micrometer 自动埋点,适用于快速接入;无侵入探针基于 LoongSuite 商业版,无需修改代码即可采集标准 OTLP 数据,解决了原生方案扩展性差、调用链易断链等问题。未来将开源无侵入探针方案,整合至 AgentScope Studio,并进一步增强多 Agent 场景下的观测能力。
1616 36
|
2月前
|
安全 Java 测试技术
《深入理解Spring》单元测试——高质量代码的守护神
Spring测试框架提供全面的单元与集成测试支持,通过`@SpringBootTest`、`@WebMvcTest`等注解实现分层测试,结合Mockito、Testcontainers和Jacoco,保障代码质量,提升开发效率与系统稳定性。
|
3月前
|
安全 IDE Java
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
本文介绍了如何在 Spring 应用程序中使用 Project Lombok 的 `@Data` 和 `@FieldDefaults` 注解来减少样板代码,提升代码可读性和可维护性,并探讨了其适用场景与限制。
156 0
Spring 的@FieldDefaults和@Data:Lombok 注解以实现更简洁的代码
|
6月前
|
设计模式 算法 架构师
京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)
京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)
京东二面:说下spring中常用的设计模式? (一个 深入骨髓的答案, 面试官跪下了)
|
6月前
|
Java 数据库连接 数据库
Spring boot 使用mybatis generator 自动生成代码插件
本文介绍了在Spring Boot项目中使用MyBatis Generator插件自动生成代码的详细步骤。首先创建一个新的Spring Boot项目,接着引入MyBatis Generator插件并配置`pom.xml`文件。然后删除默认的`application.properties`文件,创建`application.yml`进行相关配置,如设置Mapper路径和实体类包名。重点在于配置`generatorConfig.xml`文件,包括数据库驱动、连接信息、生成模型、映射文件及DAO的包名和位置。最后通过IDE配置运行插件生成代码,并在主类添加`@MapperScan`注解完成整合
1140 1
Spring boot 使用mybatis generator 自动生成代码插件
|
5月前
|
安全 Java Nacos
0代码改动实现Spring应用数据库帐密自动轮转
Nacos作为国内被广泛使用的配置中心,已经成为应用侧的基础设施产品,近年来安全问题被更多关注,这是中国国内软件行业逐渐迈向成熟的标志,也是必经之路,Nacos提供配置加密存储-运行时轮转的核心安全能力,将在应用安全领域承担更多职责。
|
6月前
|
人工智能 前端开发 Java
Java 面试资料中相关代码使用方法与组件封装方法解析
这是一份详尽的Java面试资料代码指南,涵盖使用方法与组件封装技巧。内容包括环境准备(JDK 8+、Maven/Gradle)、核心类示例(问题管理、学习进度跟踪)、Web应用部署(Spring Boot、前端框架)、单元测试及API封装。通过问题库管理、数据访问组件、学习进度服务和REST接口等模块化设计,帮助开发者高效组织与复用功能,同时支持扩展如用户认证、AI推荐等功能。适用于Java核心技术学习与面试备考,提升编程与设计能力。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
158 6
Java 面试资料中相关代码使用方法与组件封装方法解析
|
6月前
|
Java 调度 流计算
基于Java 17 + Spring Boot 3.2 + Flink 1.18的智慧实验室管理系统核心代码
这是一套基于Java 17、Spring Boot 3.2和Flink 1.18开发的智慧实验室管理系统核心代码。系统涵盖多协议设备接入(支持OPC UA、MQTT等12种工业协议)、实时异常检测(Flink流处理引擎实现设备状态监控)、强化学习调度(Q-Learning算法优化资源分配)、三维可视化(JavaFX与WebGL渲染实验室空间)、微服务架构(Spring Cloud构建分布式体系)及数据湖建设(Spark构建实验室数据仓库)。实际应用中,该系统显著提升了设备调度效率(响应时间从46分钟降至9秒)、设备利用率(从41%提升至89%),并大幅减少实验准备时间和维护成本。
363 0