Spring 循环依赖解析
Spring 循环依赖解析
什么是循环依赖
循环依赖就是两个或两个以上的对象相互依赖,形成了环状的依赖关系。
class A{
public B b;
}
class B{
public A a;
}
A a = new A();
B b = new B();
a.b = b;
b.a = a;
上述代码描述了两个对象之间的相互依赖关系。
由于在 Spring 中,Bean 的生命周期是由 Spring 进行管理,所以这种依赖关系在 Bean 的创建过程中,就会出现循环依赖的问题。在 Spring 中出现循环依赖的场景由多种,其中 Spring 会解决部分循环依赖问题,而 Spring 无法解决的,就需要在开发中避免或自己去解决。
Bean 的生命周期
Bean 的生命周期可以简化为以下几点:
- Spring 扫描 class 得到
BeanDefinition
- 根据
BeanDefinition
,通过反射调用构造方法,生成 bean 的原始对象(可能会发生构造器循环依赖)。 - 填充原始对象中的属性,也就是依赖注入(可能会发生属性循环依赖)
- 初始化回调方法
- 进行初始化前、初始化、初始化后操作
- 把最终生成的代理对象放入单例池
singletonObjects
构造器循环依赖
构造器循环依赖 :两个或多个 Bean 通过构造函数相互依赖时,就会发生构造器循环依赖。例如,BeanA 的构造函数参数需要 BeanB,而 BeanB 的构造函数参数需要 BeanA。这种循环依赖是无法解决的,因为在创建 Bean 实例时需要提供所有的构造函数参数,而循环依赖导致无法满足这个条件。
代码实例如下:
@Component
public class AService {
public BService bService;
public AService(BService bService) {
this.bService = bService;
}
public void test() {
System.out.println("bService = " + bService);
}
}
@Component
public class BService {
public AService aService;
public BService(AService aService) {
this.aService = aService;
}
public void test() {
System.out.println("aService = " + aService);
}
}
上述代码是存在构造器循环依赖的。
产生原因:
- 当 Spring 实例化 AService 对象时,会推断使用 AService 中的哪一个构造方法,因为重写了该类的构造方法,该类的空参构造被覆盖掉了,因此 Spring 只能选择该构造方法;
- 使用该构造方法时,发现形参为 BService,Spring 首先会调用
getBean()
方法,在容器内寻找BService; - 此时容器内还未实例化 BService,因此无法在容器内找到 BService,然后 Spring 会调用
doCreateBean()
方法,去实例化 BService; - 当实例化 BService时,又会重复上述过程,出现报错。
解决方法:
在 AService 中的构造方法上,添加 @Lazy
注解。
当 AService 中的构造方法,添加该注解后,在调用 AService 的构造方法时,Spring 后创建一个 BService 的代理对象,给 AService 的构造方法去使用,以此来实例化 AService,并完成后续的生命周期。
属性循环依赖
代码示例
@Component
public class AService {
public BService bService;
public void test() {
System.out.println("bService = " + bService);
}
}
@Component
public class BService {
public AService aService;
public void test() {
System.out.println("aService = " + aService);
}
}
上述代码存在属性方法间的循环依赖,该类循环依赖,由 Spring 通过三级缓存方式,进行了解决。
产生原因(假设不存在三级缓存):
- AService 并没有新增构造方法,因此 Spring 采用默认的无参构造去进行对象的实例化,实例化对象后,进入属性注入环节。
- AService 中 有 BService 属性,Spring 会在容器中寻找 BService 对象。
- 由于 Spring 中不存在 BServce 对象,会去执行创建 BService 的流程。
- 在创建 BSerivce 的流程中,发现 AService 并没有存在,会去进行创建 AService,从而循环上述问题,出现循环依赖。
解决思路:
添加一层缓存进行解决:
在对象进行实例化后,将实例化好的对象,提前暴露,放到一个缓存中,后续有对象需要该对象,去缓存中寻找。
如下图所示:
为什么需要三级缓存
通过上述流程可以看出,二级缓存就能够解决 Spring 的属性循环依赖,为什么需要三级缓存呢?
假设A的原始对象为@00a1,A并且需要进行AOP,得到的代理对象为@00a2。
由于B对象中的A对象,得到的是A的原始对象,并非代理对象。
此时就会出现不一致问题,即 B.A = @00a1,通过容器获取的A对象为@00a2。
因此需要三级缓存。
三级缓存
一级缓存:singletonObjects,也就是平时所说的单例池,存放的经过完整的生命周期的 bean 对象。
二级缓存:earlySingletonObjects,存放的是早期的 bean 对象,是没有经过完整生命周期的 bean 对象。
三级缓存:singletonFactories,对象工厂,用于创建早期 bean 对象的工厂。
三级缓存执行流程
- 首先进行实例化,然后判断该对象是否满足条件,若满足,则加入三级缓存中。
// 1.单例bean;2.允许循环依赖;3.正在创建的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 循环依赖-添加到三级缓存,此处缓存的val是lamda表达式,当需要从三级缓存中获取bean时,才会执行该表达式
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
- 填充 BService 属性,在容器中查询该对象,若找不到,则进行创建
(2.1) 实例化 BService 对象,判断是否满足条件,若满足,则加入到三级缓存中。
(2.2) 填充 AService 属性,流程见下方getSingleton()
方法解析
(2.3) 填充其他属性
(2.4) 执行初始化流程
(2.5) 放入单例池中
/*
该方法主要流程:
1.首先查询一级缓存,若存在,则返回,否则去查询二级缓存
2.查询二级缓存,若存在,则返回,否则通过三级缓存去创建
3.创建完毕,将不完整的bean,放入二级缓存,删除三级缓存中相应信息,并返回
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先去一级缓存查询,判断是否可以查询到
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 若查询不到,则去二级查询去找
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// 加锁,重复上述流程,保证原子性
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 若一二级缓存中都没有该bean,则通过三级缓存创建bean
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 放入二级缓存,此bean没有经过完整的生命周期,不能放入一级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从三级缓存中移除该bean的相关信息
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
- 填充其他属性
- 执行初始化流程
- 放入单例池中
此时便通过三级缓存机制,解决了 Spring 的属性循环依赖问题。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!