欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > Spring 如何解决循环依赖以及那些无法解决的循环依赖

Spring 如何解决循环依赖以及那些无法解决的循环依赖

2025/2/25 2:33:09 来源:https://blog.csdn.net/lrh35/article/details/145691625  浏览:    关键词:Spring 如何解决循环依赖以及那些无法解决的循环依赖

在 Spring 框架的开发过程中,循环依赖是一个常见且需要谨慎处理的问题。今天咱们就来深入探讨一下 Spring 是如何解决循环依赖的,以及哪些情况下循环依赖是无法解决的。

一、什么是循环依赖

循环依赖,简单来说,就是两个或两个以上的 bean 相互持有对方的引用,最终形成一个闭环。比如,A bean 依赖 B bean,而 B bean 又依赖 A bean,这就像两个人互相拉着对方的手,谁也没办法先迈出第一步。在 Spring 中,注入 bean 的方式主要有构造器注入和 field 属性注入,不同的注入方式在面对循环依赖时表现各异。

二、Spring 如何解决 field 属性注入的循环依赖

Spring 解决 field 属性注入循环依赖的核心机制是三级缓存和 Java 的引用传递,同时采用了提前暴露对象和延后设置属性的策略。

假设我们有两个类 A 和 B,它们通过 field 属性注入形成了循环依赖:

收起

java

import org.springframework.stereotype.Service;@Service
public class A {@Autowiredprivate B b;
}@Service
public class B {@Autowiredprivate A a;
}

当 Spring 容器启动创建 bean 时,大致过程如下:

  1. 创建 A 对象并放入三级缓存:Spring 首先通过反射创建 A 对象的实例,这个时候 A 对象还不完整,它的 b 属性还是 null。但 Spring 会把这个 “半成品” A 对象提前暴露到三级缓存(singletonFactories)中。
  2. 触发 B 对象的创建:A 对象需要填充 b 属性,于是去缓存中查找 B 对象。由于此时 B 对象还未创建,就会触发 B 对象的创建流程。
  3. 创建 B 对象并放入三级缓存:B 对象创建后也被放入三级缓存,此时 B 对象的 a 属性同样为空。
  4. 完成 B 对象的属性填充:B 对象在填充 a 属性时,会从缓存中找到之前提前暴露的 A 对象,并完成注入。
  5. 完成 A 对象的属性填充:最后,A 对象从缓存中顺利获取到 B 对象,完成 b 属性的填充。至此,循环依赖问题得以解决。

这里的三级缓存各司其职:

  • 一级缓存(singletonObjects:存放的是完整的、可以直接使用的 bean。从这里取出的 bean,就像是装配好所有零件、可以直接投入使用的机器。
  • 二级缓存(earlySingletonObjects:存放提前暴露的 bean,但这些 bean 还不完整,尚未完成属性注入和执行 init 方法,主要用于解决循环依赖。它就像是半成品,虽然有了大致的框架,但还缺少一些关键的部分。
  • 三级缓存(singletonFactories:对初始化后的 bean 完成 AOP 代理操作。它保证了 bean 的生命周期,确保在合适的时机生成代理,而不是在实例化之后就立即生成,避免影响 bean 的正常功能。

三、Spring 如何解决构造器注入的循环依赖

构造器注入的循环依赖相对复杂一些,Spring 无法像处理 field 属性注入那样直接解决,需要借助一些额外的手段。常见的解决方式有两种:使用@ScopeproxyMode属性和@Lazy注解。

(一)使用 @Scope 的 proxyMode 属性

收起

java

import org.springframework.stereotype.Service;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;@Service
public class A {private B b;@Autowiredpublic A(B b) {this.b = b;}
}@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Service
public class B {private A a;@Autowiredpublic B(A a) {this.a = a;}
}

@Scope注解用于定义 bean 的作用域,默认是单例(singleton)。通过proxyMode属性可以设置类的代理模式,这里使用ScopedProxyMode.TARGET_CLASS表示使用 CGLIB 动态代理。这种方式下,Spring 通过创建代理对象打破循环依赖,使得 A 和 B 能够顺利创建。

(二)使用 @Lazy 注解

收起

java

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Lazy;@Service
public class A {private B b;@Autowiredpublic A(@Lazy B b) {this.b = b;}
}@Service
public class B {private A a;@Autowiredpublic B(A a) {this.a = a;}
}

@Lazy注解的作用是让 bean 延迟加载。当 A 依赖 B 时,由于 B 被标记为延迟加载,Spring 会创建一个 B 的代理对象 Bproxy 来满足 A 的依赖。这样,A 依赖的是 Bproxy,而不是真实的 B 对象,打破了循环依赖。之后创建 B 时,因为 A 已经存在,B 也能成功创建。

四、无法解决的循环依赖

虽然 Spring 提供了多种解决循环依赖的方法,但有些情况下循环依赖确实无法解决。

最典型的就是构造器注入的循环依赖,如果没有使用上述提到的特殊手段(@ScopeproxyMode属性或@Lazy注解),Spring 在启动时会抛出BeanCurrentlyInCreationException异常。这是因为构造器注入要求在创建 bean 时就完成所有依赖的注入,而循环依赖使得这个过程陷入了死锁,Spring 无法确定先创建哪个 bean。

另外,在一些复杂的自定义 BeanPostProcessor 场景中,如果自定义的后置处理器逻辑与循环依赖相互干扰,也可能导致循环依赖问题无法解决。比如,后置处理器在 bean 初始化过程中对依赖关系进行了错误的修改,使得 Spring 原本的解决机制失效。

五、总结

Spring 通过三级缓存机制巧妙地解决了 field 属性注入的循环依赖问题,同时借助动态代理(@ScopeproxyMode属性和@Lazy注解)来处理构造器注入的循环依赖。然而,我们也要清楚地认识到,并非所有的循环依赖都能被 Spring 轻松化解。在实际开发中,合理设计 bean 之间的依赖关系,避免不必要的循环依赖,才是编写健壮 Spring 应用的关键。希望通过这篇博客,大家对 Spring 的循环依赖问题有更深入的理解,在开发中能够更加游刃有余地应对相关挑战。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词