编码解决
通过良好的编码习惯可以避免Spring循环依赖
方式主要有三种:
1. 构造函数注入: 这是最佳实践,通过构造函数注入可以避免循环依赖。在一个类的构造函数中注入其依赖,而不是通过字段注入。
@Service
public class A {
private final B b;
@Autowired
public A(B b) {
this.b = b;
}
}
@Service
public class B {
private final A a;
@Autowired
public B(A a) {
this.a = a;
}
}
2. Setter方法注入: 使用Setter方法注入可以解决一部分循环依赖的问题,但不如构造函数注入优雅。
@Service
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
@Service
public class B {
private A a;
@Autowired
public void setA(A a) {
this.a = a;
}
}
3. @Lazy注解: 通过在其中一个依赖上使用@Lazy注解,可以延迟初始化被注入的Bean,从而解决循环依赖。
@Service
public class A {
@Autowired
@Lazy
private B b;
}
@Service
public class B {
@Autowired
@Lazy
private A a;
}
为什么构造函数注入可以解决循环依赖?
Spring 容器创建一个 Bean 的过程包括以下步骤:
- 实例化 Bean 对象: Spring 容器根据配置或注解等方式,实例化一个 Bean 对象。这一步是通过调用 Bean 对应类的构造函数完成的。
构造函数注入能够解决循环依赖的原因主要是因为在 Java 对象创建的过程中,构造函数是最先被调用的,而在构造函数中完成了对象的初始化工作。通过构造函数注入,Spring 在创建对象时可以确保依赖的对象已经初始化完毕,避免了循环依赖的问题。
- 依赖注入: 容器对 Bean 进行属性注入。这可以通过构造函数注入、Setter 方法注入或字段注入等方式完成。这是实现控制反转(IoC)的关键步骤,将依赖关系从应用程序代码中解耦,由容器负责管理。
因为通过构造函数注入,在依赖注入环节需要注入的Bean在上一个实例化阶段就初始化完毕了,就不存在循环依赖问题了
BeanPostProcessor 处理: 如果在容器中注册了 BeanPostProcessor,Spring 会在实例化 Bean 以及依赖注入之后调用它们的回调方法。这提供了一种机制,使开发者可以在 Bean 实例创建的不同生命周期阶段进行定制操作。
初始化方法调用: 如果 Bean 实现了 InitializingBean 接口,或者在配置文件中通过 <init-method> 或 @PostConstruct 注解指定了初始化方法,Spring 会在依赖注入之后调用初始化方法。这个阶段是为了让开发者执行一些初始化逻辑。
BeanPostProcessor 处理(后置初始化): 如果注册了 BeanPostProcessor,Spring 会在初始化方法调用后再次调用它们的回调方法。这一步称为后置初始化,提供了对 Bean 进行最后处理的机会。
将 Bean 放入容器: 容器将创建完成并初始化的 Bean 放入自己的 Bean 容器中,以便供其他 Bean 或组件进行依赖注入。
那三级缓存解决循环依赖是怎么回事?
Spring 中通过三级缓存(三级Map)的方式来解决循环依赖问题。这三级缓存的主要作用是在处理 Bean 的创建过程中,记录 Bean 的创建状态,避免循环依赖导致的死循环。
三级缓存包括:
singletonObjects: 保存完全初始化并准备就绪的单例 Bean。在这个 Map 中,Bean 的名字作为键,Bean 实例作为值。在正常情况下,一个单例 Bean 只会放入该缓存一次。
earlySingletonObjects: 保存已经实例化但未完成初始化的 Bean。这是为了解决循环依赖的问题。在这个阶段,Bean 实例被提前暴露给其他 Bean,但尚未执行完整的初始化。同样,Bean 的名字作为键,Bean 实例作为值。
singletonFactories: 保存 Bean 工厂的提供者,即创建 Bean 的工厂对象。同样,Bean 的名字作为键,Bean 工厂实例作为值。这个阶段主要用于处理循环依赖。
下面是 Spring 处理循环依赖的基本流程:
- 尝试获取 Bean:
首先,从 singletonObjects 中尝试获取 Bean。如果找到,直接返回。
如果 singletonObjects 中没有找到,再尝试从 earlySingletonObjects 中获取 Bean。如果找到,直接返回。这是为了解决循环依赖的问题,即使 Bean 尚未完全初始化,也可以提前暴露给其他 Bean 使用。
- 创建 Bean:
如果在 singletonObjects 和 earlySingletonObjects 中都没有找到 Bean,则从 singletonFactories 中获取 ObjectFactory,然后使用工厂创建 Bean 的原始实例。
在创建过程中,将正在创建的 Bean 放入 earlySingletonObjects。
- 初始化 Bean:
完成 Bean 的创建和初始化后,将 Bean 从 earlySingletonObjects 移动到 singletonObjects 中,表示 Bean 创建完成。
所以,earlySingletonObjects 中的 Bean 是在 Bean 创建的过程中,尚未完成初始化的实例,目的是为了提前处理循环依赖的情况。
总结起来,earlySingletonObjects 在 Bean 创建的过程中使用,用于解决循环依赖。在整个生命周期中,Bean 的状态会在这三个缓存之间转移。