Java-37 深入浅出 Spring - IoC容器体系 循环依赖 原型Bean 原型作用域 Lazy ObjectFactory
点一下关注吧!!!非常感谢!!持续更新!!!
大数据篇正在更新!https://blog.csdn.net/w776341482/category_12713819.html
章节内容
上节我们完成了:
- BeanFactory过程分析
- Bean Lazy-Init
Spring IoC 循环依赖
基本介绍
循环依赖是循环引用,也就是两个或者两个以上的 Bean 互相持有对方,最终形成环:
注意,这里不是函数的循环调用,是对象的相互依赖关系,循环调用其实是一个死循环,除非有终止条件。
Spring 中循环以来的场景有:
- 构造器的循环依赖(构造器注入)
- Field 属性的循环依赖(set 方法注入)
其中,构造器的循环依赖无法解决,只能抛出:BeanCurrentlyInCreationException 的异常,在解决属性循环以来的问题的时候,Spring 采用的是提前暴露对象的方法。
处理机制
- 单例 Bean 构造器 参数循环依赖(无法解决)
- prototype 原型 Bean 循环依赖(无法解决)
原型 Bean
对于原型 Bean 的初始化过程中不论是通过构造器参数循环依赖还是通过 setXxx 方法产生依赖,Spring 都会直接报错。
在 AbstractBeanFactory.doGetBean() 方法中有这么一段:
其中 isPrototypeCurrentlyInCreation 函数内容如下:
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
在获取 Bean 之前,如果这个 Bean 正在被创建则直接异常,原型 Bean 在创建之前会进行标记,表明这个 beanName 正在被创建吗,等创建结束之后就会删除标记。
这里就是创建之后,对 beanName 的移除。
总结一下:Spring 不支持原型 Bean 的循环依赖。
setXxx 与 Autowired
Spring 的循环依赖的理论依据基于 Java 的引用传递,当获得对象的应用时,对象的属性时可以延后设置的,但是构造器必须是在获取引用之前。
Spring 通过 setXxx 或者 Autowired 方法解决循环依赖其实是通过提前暴露一个 ObjectFactory 对象来完成的,简单来说 ClassA 在调用构造器完成对象初始化之后,在调用 ClassA 的 setClassB 方法之前就把 ClassA 实例化的对象通过 ObjectFactory 提前暴露到 Spring 容器。
Spring 容器初始化 ClassA 通过构造器初始化对象后提前暴露到 Spring 容器。
- ClassA 调用 setClassB 方法,Spring 首先尝试从容器中获取 ClassB,此时 ClassB 不存在 Spring 容器中
- Spring 容器初始化 ClassB,同时也会将 ClassB 提前暴露到 Spring 容器中
- ClassB 调用 setClassA 方法,Spring 从容器中获取 ClassA,因为第一步中已经提前暴露了 ClassA,因此可以获取到 ClassA 实例
- ClassA 和 ClassB 都完成了对象初始化操作,解决了循环依赖问题
最后小结
为什么原型无法解决循环依赖
在 Spring 中,当 Bean 被定义为 原型(Prototype)作用域 时,循环依赖问题无法解决。这是因为原型作用域的 Bean 不像单例作用域的 Bean 那样会被缓存,Spring 容器无法预先实例化并缓存它们的引用,从而在循环依赖场景下无法完成依赖注入。
单例作用域如何解决循环依
在单例作用域下,Spring 容器采用了三级缓存来解决循环依赖问题:
- 第一级缓存:存储完全初始化好的单例 Bean(singletonObjects)。
- 第二级缓存:存储提前暴露的 Bean 实例(earlySingletonObjects),避免完全初始化前的重复创建。
- 第三级缓存:存储 Bean 的 ObjectFactory,用于动态创建 Bean。
在创建 Bean 时,如果发现循环依赖,Spring 可以通过提前暴露尚未完全初始化的 Bean 引用,解决循环依赖问题。
如何解决原型作用域的循环依赖?
由于 Spring 无法自动解决原型作用域的循环依赖问题,以下是一些常见的解决方法:
方法 1:重构设计,避免循环依赖
通常,循环依赖本身是一种设计问题,可以通过修改依赖关系来解决。例如:
- 引入第三方服务作为中介。
- 使用事件驱动或回调机制替代直接依赖。
方法 2:手动注入依赖
通过代码手动注入依赖,避免在 Bean 初始化时依赖注入。
@Component
@Scope("prototype")
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
方法 3:使用 @Lazy 延迟加载
通过延迟初始化的方式,让依赖在需要时才被创建,避免循环依赖在初始化时发生。
@Component
@Scope("prototype")
public class A {
@Autowired
@Lazy
private B b;
}
@Component
@Scope("prototype")
public class B {
@Autowired
@Lazy
private A a;
}
方法 4:通过 ObjectFactory 或 Provider 动态获取
使用 Spring 的 ObjectFactory 或 javax.inject.Provider 来延迟获取 Bean。
@Component
@Scope("prototype")
public class A {
@Autowired
private ObjectFactory<B> bFactory;
public B getB() {
return bFactory.getObject();
}
}
@Component
@Scope("prototype")
public class B {
@Autowired
private ObjectFactory<A> aFactory;
public A getA() {
return aFactory.getObject();
}
}
方法 4:通过 ObjectFactory 或 Provider 动态获取
使用 Spring 的 ObjectFactory 或 javax.inject.Provider 来延迟获取 Bean。
@Component
@Scope("prototype")
public class A {
@Autowired
private ObjectFactory<B> bFactory;
public B getB() {
return bFactory.getObject();
}
}
@Component
@Scope("prototype")
public class B {
@Autowired
private ObjectFactory<A> aFactory;
public A getA() {
return aFactory.getObject();
}
}