你好,我是风一样的树懒,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。
在 Spring 中,循环依赖(Circular Dependency)是指两个或多个 Bean 相互依赖,形成一个循环关系。在构造器注入(Constructor Injection)时,Spring 容器无法正常解析这些 Bean,因为它们相互等待对方实例化。在这种情况下,Spring 提供了几种方法来解决循环依赖问题。
当使用构造器注入时,Spring 容器会尝试通过构造方法来实例化对象。如果两个 Bean 互相依赖,Spring 无法判断应该先实例化哪个 Bean,因为它们的实例化需要对方作为构造参数。
Spring 容器通过以下几种方式来解决循环依赖问题,具体方式取决于使用的是 构造器注入 还是 Setter 注入。
Setter 注入的方式是通过依赖注入的 setter 方法来注入 Bean 的依赖,而不是在构造方法中传入。这使得 Spring 能够在 Bean 的实例化之后,设置相应的依赖关系,避免了构造器注入中的循环依赖问题。
Spring 解决循环依赖的基本原理是:在创建 Bean 的过程中,Spring 容器会先实例化 Bean(不进行依赖注入),然后再通过 setter 方法进行依赖注入。
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
Spring 解决循环依赖的流程如下:
Spring 容器首先创建 Bean A 和 Bean B,但是在实例化时不注入依赖。
然后,Spring 会处理 setter 方法,将 Bean B 注入到 Bean A 中,将 Bean A 注入到 Bean B 中。
最终,所有依赖注入完成后,Bean A 和 Bean B 都会被完全初始化。
在构造器注入中,Spring 5 引入了 ObjectFactory 和 @Lazy 注解来处理循环依赖。
@Lazy 注解:Spring 会使用 @Lazy 注解来延迟某些依赖的注入。这样,Spring 容器会在需要的时候(而不是在 Bean 初始化时)才去注入这些依赖,从而避免了循环依赖。
public class A {
private final B b;
public A( B b) {
this.b = b;
}
}
public class B {
private final A a;
public B( A a) {
this.a = a;
}
}
在这个例子中,@Lazy 注解告知 Spring 容器不要立即注入 A 和 B,而是等到它们真正需要的时候再进行注入,从而避免了循环依赖问题。
Spring 还提供了 ObjectFactory,它允许你获取 Bean 的延迟引用。当 Spring 在创建 Bean 时发现循环依赖,ObjectFactory 会返回一个代理对象,在实际访问该 Bean 时再进行注入。
public class A {
private final ObjectFactory<B> bFactory;
public A(ObjectFactory<B> bFactory) {
this.bFactory = bFactory;
}
public void doSomething() {
// 延迟注入
B b = bFactory.getObject();
// 使用 b
}
}
@Component
public class B {
private final ObjectFactory<A> aFactory;
@Autowired
public B(ObjectFactory<A> aFactory) {
this.aFactory = aFactory;
}
public void doSomething() {
A a = aFactory.getObject(); // 延迟注入
// 使用 a
}
}
Spring 解决循环依赖的关键是 三级缓存 机制:
实例化对象:Spring 会创建一个空的 Bean 实例,但没有完全初始化 Bean,这个实例化过程会被放入 Spring 的一个缓存中。
依赖注入:Spring 会通过反射和 setter 方法将依赖注入到空的 Bean 中。
完成初始化:一旦所有的依赖都注入完成,Spring 会将 Bean 放入完全初始化的缓存中。
通过这种方式,Spring 可以在没有完全初始化对象的情况下将其添加到缓存中,然后解决循环依赖问题。
Setter 注入:在解决循环依赖时,最推荐使用 Setter Injection。Spring 会先实例化对象,再注入依赖。
构造器注入:构造器注入中,Spring 5 以后可以通过 @Lazy 或 ObjectFactory 来避免循环依赖。
三级缓存:Spring 使用三级缓存来管理 Bean 的实例化、依赖注入和初始化过程,从而解决循环依赖的问题。
虽然 Spring 解决循环依赖的能力是强大的,但在实际开发中,建议避免设计存在循环依赖的代码。循环依赖通常意味着类之间的关系过于紧密,可能需要重新审视设计。
今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!