【SpringBoot】IOC如何解决Bean的循环依赖

简介: 面试官: 作为一个Java练习两年半的练习生,IOC如何解决Bean的循环依赖这种老八股已经形成了一套丝滑小连招。

SpringBoot相关文章


SpringBean的生命周期

参照上篇文章【SpringBean的生命周期】,Spring的一些常考问题其实是环环相扣的,IOC容器解决循环依赖也是依靠了一些生命周期的东西。这里可以大体回顾一下。

  • bean生命周期的三个过程
  1. beanDefinition
  2. 实例化:执行了bean的构造方法,bean中依赖的对象还未赋值
  3. 设置属性:给bean中依赖的对象赋值,若被依赖的对象尚未初始化,则先进行该对象的生命周期(递归)
  4. 初始化:执行bean的初始化方法,回调方法等


就算没研究过如何解决循环依赖那你也肯定听说过一个叫做三级缓存的东西,如果真的没听说过,那你现在肯听听说了。。。


三级缓存


三级缓存核心是通过将bean的实例化状态(执行完bean的构造方法)提前暴露存入Map中,如果遇到互相依赖的情况通过在Map中取出实例化尚不完整的bean完成当前实例化。


类似递归的思想,挨个排查依赖的对象是否存在于缓存中,但仅仅适用于单例模式,多例模式的bean不使用缓存,循环依赖无法解决。

/** 一级缓存,缓存bean的地方,bean都是完整可用的 */privatefinalMap<String, Object>singletonObjects=newConcurrentHashMap<>(256);
/** 二级缓存,缓存实例化后不完整的bean*/privatefinalMap<String, Object>earlySingletonObjects=newHashMap<>(16);
/** 三级缓存,缓存key为beanName, value为ObjectFactory处理aop*/privatefinalMap<String, ObjectFactory<?>>singletonFactories=newHashMap<>(16);

举个例子,假如A类中引入了B类,B类又引入了A类。那么bean创建时的步骤:

  1. 实例化A的时候,调用A的构造方法,创建出A对象存放到三级缓存中,虽然属性都没有初始化,但是已经注册在IOC容器中。
  2. 对A对象设置属性时,发现A对象依赖了B对象,那么就去创建B对象,按照流程再走一遍实例化,同样会把B实例化不完整对象放入三级缓存中。当B设置属性的时候,发现依赖了A类,这时候再去创建A对象,发现在三级缓存(singletonFactories)能找到A的实例化不完整对象。
  3. 此时会通过A的ObjectFactory获取A,并把A从三级缓存移到二级缓存。然后就可以把B的A属性赋值了,这个时候B就初始化完成了,初始化完成后就会把B从三级缓存移到一级缓存。
  4. 完成B实例化后,回到A调用的设置属性流程中。返回的就是B对象了,对A的B属性进行赋值就可以了。


为什么需要三级缓存

按照上述的描述,好像两个缓存就可以满足循环依赖问题,一个存放半状态bean,一个存放完整bean。不过前提是这个bean没有被AOP进行代理。


三级缓存本质上是为了存放bean的代理对象保证代理对象的单例属性,还是回到刚才的A类和B类相互依赖的场景下,在第三步通过A的ObjectFactory接口获取A对象本质上是通过调用getEarlyBeanReference()方法,这个方法会返回一个A类的AOP对象回去。


问题出在每次调用getEarlyBeanReference方法,每次都会返回一个新的AOP对象,假如没有二级缓存,C类也依赖A类,调用getEarlyBeanReference()方法则注入了一个新的A类的AOP对象,并且与B类注入的A类对象是两个不同的对象,完全违背单例模式。


相关文章
|
11天前
|
Java
SpringBoot构建Bean(RedisConfig + RestTemplateConfig)
SpringBoot构建Bean(RedisConfig + RestTemplateConfig)
31 2
|
1月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
1月前
|
Java Spring 容器
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
这篇文章讨论了在Spring Boot 3.2.1版本中,同名同类型的bean和@Service注解类之间冲突的问题得到了解决,之前版本中同名bean会相互覆盖,但不会在启动时报错,而在配置文件中设置`spring.main.allow-bean-definition-overriding=true`可以解决这个问题。
85 0
Springboot3.2.1搞定了类Service和bean注解同名同类型问题修复
|
2月前
|
Java Spring
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
|
1月前
|
Java Shell C++
Springboot加载注入bean的方式
本文详细介绍了Spring Boot中Bean的装配方法。首先讲解了使用@Component、@Service、@Controller、@Repository等注解声明Bean的方式,并解释了这些注解之间的关系及各自适用的层次。接着介绍了通过@Configuration和@Bean注解定义Bean的方法,展示了其灵活性和定制能力。最后讨论了@Component与@Bean的区别,并提供了在Spring Boot应用中装配依赖包中Bean的三种方法:使用@ComponentScan注解扫描指定包、使用@Import注解导入特定Bean以及在spring.factories文件中配置Bean。
|
4月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
103 11
|
4月前
|
消息中间件 Java Kafka
Spring boot 自定义kafkaTemplate的bean实例进行生产消息和发送消息
Spring boot 自定义kafkaTemplate的bean实例进行生产消息和发送消息
187 5
|
3月前
|
Java Spring 容器
Java SpringBoot 中,动态执行 bean 对象中的方法
Java SpringBoot 中,动态执行 bean 对象中的方法
40 0
|
3月前
|
Java Spring
Java SpringBoot Bean InitializingBean 项目初始化
Java SpringBoot Bean InitializingBean 项目初始化
54 0
|
4月前
|
Java Maven
SpringBoot第一次导入项目,Maven依赖全爆红,该怎样解决,idea2019.3版本,必须用application2.7.6或者以下
SpringBoot第一次导入项目,Maven依赖全爆红,该怎样解决,idea2019.3版本,必须用application2.7.6或者以下