高级java每日一道面试题-2025年01月13日-框架篇[Spring篇]-Spring 是怎么解决循环依赖的?

如果有遗漏,评论区告诉我进行补充

面试官: Spring 是怎么解决循环依赖的?

我回答:

在Java高级面试中,Spring框架如何解决循环依赖是一个重要且常见的问题。以下是对Spring解决循环依赖的详细解释:

循环依赖的定义与类型

循环依赖是指两个或多个Bean之间互相依赖,形成一个闭环。在Spring框架中,循环依赖通常发生在依赖注入(Dependency Injection)过程中。循环依赖的类型主要包括构造函数循环依赖和属性(或Setter)循环依赖。

Spring 如何检测和解决循环依赖?

Spring 使用了多种策略来检测并解决循环依赖问题,主要包括以下几种方式:

单例 Bean 的三级缓存机制

Spring 容器为每个单例 Bean 维护了三个不同级别的缓存:

  • singletonObjects:存放已经完全初始化完成的 Bean 实例。
  • earlySingletonObjects:存放尚未完成初始化但已经被实例化的 Bean(即半成品 Bean)。这些 Bean 已经完成了构造函数注入,但是还没有完成属性注入和其他初始化方法调用。
  • singletonFactories:存放 FactoryBean 或者用于创建早期暴露对象的工厂方法。

当 Spring 检测到 A 和 B 之间的循环依赖时,它会按照如下步骤操作:

  1. 创建 Bean A :Spring 开始创建 Bean A,并将其放入 singletonFactories 中。
  2. 实例化 Bean A:接着实例化 Bean A 并设置其非循环依赖的属性。
  3. 提前暴露 Bean A :将 Bean A 提前暴露给其他 Bean 使用,此时 Bean A 被移入 earlySingletonObjects 缓存。
  4. 创建 Bean B:尝试创建 Bean B,在此过程中发现它依赖于 Bean A。
  5. 获取 Bean A :由于 Bean A 已经存在于 earlySingletonObjects 中,所以可以直接获取并设置到 Bean B 上。
  6. 继续初始化 Bean A :回到 Bean A 的初始化流程,设置它的剩余属性(包括对 Bean B 的引用),最后将 Bean A 移入 singletonObjects 缓存。
  7. 完成 Bean B 的初始化 :现在可以安全地完成 Bean B 的初始化,因为它已经获得了对 Bean A 的引用。
    通过这种方式,Spring 成功地打破了循环依赖,使得两个相互依赖的 Bean 都能正常初始化。
构造器注入 vs 字段/Setter 注入
  • 构造器注入:如果使用构造器注入,则在构造函数中传递所有必需的依赖项。在这种情况下,如果存在循环依赖,Spring 将抛出异常,因为无法同时满足两个 Bean 的构造需求。
  • 字段/Setter 注入:相比之下,字段注入或 Setter 方法注入允许 Spring 在实例化之后再设置依赖关系,这为解决循环依赖提供了可能。因此,推荐在需要支持循环依赖的情况下优先考虑字段或 Setter 注入。
@Lazy 注解

对于某些特定场景下的循环依赖问题,可以使用 @Lazy 注解延迟加载某个 Bean,直到真正需要它的时候才进行实例化。这样可以避免在启动阶段就触发循环依赖错误。

java 复制代码
@Component
public class ServiceA {
    private final ServiceB serviceB;

    @Autowired
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}
代理模式

Spring 还可以通过 CGLIB 动态代理的方式生成目标 Bean 的代理类,然后在代理类中实现对原始 Bean 的懒加载。这种方式适用于接口类型的 Bean,能够有效缓解循环依赖问题。

Spring无法解决的循环依赖情况

需要注意的是,Spring的循环依赖解决机制有一些限制:

  1. 原型作用域的Bean:对于原型作用域的Bean,由于每次请求都会创建一个新的Bean实例,因此无法使用缓存来解决循环依赖。
  2. 构造器注入的循环依赖:如果Bean的构造方法中存在循环依赖,Spring也无法解决。因为在构造方法中,Bean实例还未创建,无法放入缓存。

解决循环依赖的最佳实践

尽管Spring提供了解决循环依赖的机制,但在设计时仍应尽量避免出现循环依赖,因为循环依赖可能导致代码的可读性差,并且可能是设计上的问题。以下是一些解决循环依赖的最佳实践:

  1. 模块化:将代码拆分成独立的模块,使每个模块只负责一个功能,降低模块间的耦合度。
  2. 使用依赖注入:通过依赖注入,将依赖关系从代码中解耦,使得一个类不再直接依赖另一个类,而是依赖于一个接口或抽象类。
  3. 使用设计模式:利用设计模式(如观察者模式、中介者模式等)来帮助更好地组织代码,避免循环依赖的产生。
  4. 代码重构:定期对代码进行重构,消除潜在的循环依赖问题。
相关推荐
风生u21 分钟前
activiti7 详解
java
岁岁种桃花儿29 分钟前
SpringCloud从入门到上天:Nacos做微服务注册中心(二)
java·spring cloud·微服务
Word码33 分钟前
[C++语法] 继承 (用法详解)
java·jvm·c++
TT哇38 分钟前
【实习 】银行经理端两个核心功能的开发与修复(银行经理绑定逻辑修复和线下领取扫码功能开发)
java·vue.js
逝水如流年轻往返染尘41 分钟前
Java中的数组
java
java1234_小锋1 小时前
Java高频面试题:BIO、NIO、AIO有什么区别?
java·面试·nio
用户8307196840821 小时前
Java IO三大模型(BIO/NIO/AIO)超详细总结
java
sheji34161 小时前
【开题答辩全过程】以 基于SSM的花店销售管理系统为例,包含答辩的问题和答案
java
Mr_sun.1 小时前
Day09——入退管理-入住-2
android·java·开发语言
MAGICIAN...2 小时前
【java-软件设计原则】
java·开发语言