面试问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都是存储对象的中间状态,依赖它们保证最终注入的对象是完整的,依赖注入完成后会被清除。

相关文章
|
2月前
|
Java 编译器 C++
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
这篇文章解释了Java能够实现“一次编写,到处运行”的原因,主要归功于Java虚拟机(JVM),它能够在不同平台上将Java源代码编译成的字节码转换成对应平台的机器码,实现跨平台运行。
【Java基础面试一】、为什么Java代码可以实现一次编写、到处运行?
|
19天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
19天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
19天前
|
缓存 Java 数据库
【Java面试题汇总】Spring篇(2023版)
IoC、DI、aop、事务、为什么不建议@Transactional、事务传播级别、@Autowired和@Resource注解的区别、BeanFactory和FactoryBean的区别、Bean的作用域,以及默认的作用域、Bean的生命周期、循环依赖、三级缓存、
【Java面试题汇总】Spring篇(2023版)
|
4天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
13 4
|
7天前
|
XML Java 开发者
经典面试---spring IOC容器的核心实现原理
作为一名拥有十年研发经验的工程师,对Spring框架尤其是其IOC(Inversion of Control,控制反转)容器的核心实现原理有着深入的理解。
29 3
|
2月前
|
Java 数据安全/隐私保护 Spring
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
揭秘Spring Boot自定义注解的魔法:三个实用场景让你的代码更加优雅高效
|
2月前
|
Java Spring XML
掌握面向切面编程的秘密武器:Spring AOP 让你的代码优雅转身,横切关注点再也不是难题!
【8月更文挑战第31天】面向切面编程(AOP)通过切面封装横切关注点,如日志记录、事务管理等,使业务逻辑更清晰。Spring AOP提供强大工具,无需在业务代码中硬编码这些功能。本文将深入探讨Spring AOP的概念、工作原理及实际应用,展示如何通过基于注解的配置创建切面,优化代码结构并提高可维护性。通过示例说明如何定义切面类、通知方法及其应用时机,实现方法调用前后的日志记录,展示AOP在分离关注点和添加新功能方面的优势。
38 0
|
2月前
|
Java Spring 容器
彻底改变你的编程人生!揭秘 Spring 框架依赖注入的神奇魔力,让你的代码瞬间焕然一新!
【8月更文挑战第31天】本文介绍 Spring 框架中的依赖注入(DI),一种降低代码耦合度的设计模式。通过 Spring 的 DI 容器,开发者可专注业务逻辑而非依赖管理。文中详细解释了 DI 的基本概念及其实现方式,如构造器注入、字段注入与 setter 方法注入,并提供示例说明如何在实际项目中应用这些技术。通过 Spring 的 @Configuration 和 @Bean 注解,可轻松定义与管理应用中的组件及其依赖关系,实现更简洁、易维护的代码结构。
35 0
|
2月前
|
Java API 开发者
【开发者福音】Spring Boot 异常处理:优雅应对错误,提升应用健壮性,让调试不再是噩梦!
【8月更文挑战第29天】本文通过对比传统错误处理方式与Spring Boot推荐的最佳实践,展示了如何在Spring Boot应用中实现统一且优雅的异常处理。传统方法需在每个可能出错的地方显式处理异常,导致代码冗余且不一致。而Spring Boot的全局异常处理机制则能集中处理所有异常,简化代码并确保错误响应格式统一,提高应用程序的健壮性和可维护性。文中提供了具体的示例代码以帮助读者更好地理解和应用这一机制。
71 0
下一篇
无影云桌面