墨风如雪博客

  • 源码小店
  • 导航站
  • 登录
  • java
  • 资源分享
让AI使用变得如此简单
  1. 首页
  2. java
  3. spring
  4. 正文

Spring 中循环依赖问题的产生原因及注意事项

2023年 5月 2日 184点热度 0人点赞 0条评论

1. 循环依赖的产生原因

Java Spring 中循环依赖的原因一般是因为两个或多个 bean 相互依赖,形成了一个循环依赖的环路。通俗地讲,A 依赖 B,B 又依赖 A。

Spring 依赖注入机制的目的是将启动时需要创建的对象(通常称为 bean)及它们之间的依赖关系全部交付给 Spring 容器进行管理和维护,通过配置(如 XML 或注解等方式)告诉 Spring 需要创建哪些对象,每个对象需要依赖哪些其它对象,Spring 运行时会自动去解决对象之间的依赖关系。

循环依赖问题是在 Spring 运行时进行对象依赖注入时引起的,如果在对象依赖注入过程中,出现了 A->B->C->A 这样的环形依赖关系,就会导致循环依赖问题的出现。

具体来讲,Spring 在进行对象注入时有两种策略:构造函数注入和 Setter 方法注入。

构造函数注入:

  1. 容器创建 Bean A 实例。
  2. 创建 Bean A 时,如果发现需要容器创建 Bean B 的实例,则会暂时将 A 对象存储到一个缓存区中,然后记录下所需注入属性的类型和 Bean 名称。
  3. 容器创建 Bean B 实例。
  4. 创建 Bean B 实例时,如果发现需要容器创建 Bean A 的实例,则会暂时将 B 对象存储到一个缓存区中,然后记录下所需注入属性的类型和 Bean 名称。
  5. 容器会尝试按照缓存区中的属性类型和名称获取对应的 Bean 进行注入,这个时候会从缓存区中获取相应的 bean,如果没有则将 A 对象加入到创建 B 对象时记录的 A 需要注入的属性的列表中去,返回到容器去继续初始化其他的 Bean,直到将所有的 Bean 初始化完成,再在之前缓存的 Bean 中进行对应的属性注入。

Setter 方法注入:

  1. 容器创建 A 实例。
  2. 容器注入 A 实例所需的属性,如果发现需要创建 B 实例,则会临时将 A 对象存储起来,并记录需要注入属性的类型和 Bean 名称。
  3. 容器创建 B 实例。
  4. 容器注入 B 实例所需的属性,如果发现需要创建 A 实例,则会临时将 B 对象存储起来,并记录需要注入属性的类型和 Bean 名称。
  5. 容器会尝试按照临时存储的对象类型和名称获取对应的 Bean 进行注入,这个时候会从临时存储的对象中获取相应的 Bean,如果没有则将 A 对象加入到创建 B 对象时记录的 A 需要注入的属性的列表中去(注意和构造函数注入的区别),返回到容器去继续初始化其他 Bean,知道将 BEan 的所有属性注入完成。

需要注意的是,循环依赖并不是所有情况都会出现的,像单例模式下的循环依赖就不会出现,这是因为 Spring 容器缓存的是对象的单例实例,而不是对象本身。

  1. 循环依赖的解决方案

解决循环依赖问题,可以使用构造函数注入或者使用@Lazy注解进行懒加载。

2.1 使用构造函数注入

使用构造函数注入,正是因为构造函数的注入是一次性的,如果需要解决循环依赖,循环依赖的双方需要在构造函数中传递一个代理对象。

假设有A和B依赖于彼此,结构代码如下:

public class A {
    private B b;

    public A(B b) {
        this.b = b;
    }

    // getters and setters
}

public class B {
    private A a;

    public B(A a) {
        this.a = a;
    }

    // getters and setters
}

我们可以使用 Spring 的构造函数注入来解决循环依赖。

<bean id="a" class="com.example.A">
    <constructor-arg ref="b"/>
</bean>

<bean id="b" class="com.example.B">
    <constructor-arg ref="a"/>
</bean>

这样,Spring 在创建 A 的时候会先创建 B,将 B 传入到 A 的构造函数中,再将 A 传入到 B 的构造函数中,这样就成功解决了循环依赖的问题。

2.2 使用 @Lazy 注解进行懒加载

使用 @Lazy 注解进行懒加载是另一种解决循环依赖的方案。在 Spring 5.x 版本中,新增了一个特性,即默认情况下支持循环依赖,它会创建代理对象,并将代理对象暂时注册到 BeanFactory 中。

示例代码如下:

@Lazy
@Component
public class A {
    @Autowired
    private B b;

    // getters and setters
}

@Lazy
@Component
public class B {
    @Autowired
    private A a;

    // getters and setters
}

这样,当创建 A 时,容器中就先存在了一个 A 的代理对象,而真正的 A 对象会在 B 创建完成后再初始化创建,因为此时 B 已经存在了一个代理对象,再把这个代理对象注入到 A 中,就成功解决了循环依赖的问题。

  1. 循环依赖的注意事项

3.1 构造注入不能解决循环依赖的问题

尽管通过构造函数注入很容易解决循环依赖的问题,但是构造注入又陷入另一个问题——它无法解决循环依赖的问题,例如 A 依赖 B,B 依赖 A,在进行构造注入的时候,即便在 A 和 B 中使用构造注入,它也无法在创建两个对象之后自动解决循环依赖。

3.2 按需注入可以解决循环依赖

按需注入指的是 Spring 在需要使用 bean 时才进行注入,而不是在创建时就注入。如果两个 bean 都使用按需注入,那么 Spring 可以在第一次访问 bean 的时候解决循环依赖的问题。

3.3 需要注意的是循环依赖不仅限于同一个 ApplicationContext

如果两个 bean 分别被不同的 ApplicationContext 创建,如果两个 ApplicationContext 之间产生了循环依赖的关系,Spring 也无法直接解决这个问题,需要手动合并这两个 ApplicationContext 或使用其他方法解决。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: spring 产生原因 循环依赖|问题 注意事项
最后更新:2023年 5月 2日

墨风如雪

一个热爱生活,热爱分享的程序员

打赏 点赞
下一篇 >

文章评论

您需要 登录 之后才可以评论

墨风如雪

一个热爱生活,热爱分享的程序员

最新 热点 随机
最新 热点 随机
美团炸场AI圈:点外卖点出个软件?用「对话式编程」重塑生产力! 当你的证件照学会了眨眼微笑:腾讯混元 HunyuanPortrait 开源,让数字肖像「活过来」! 办公三件套颤抖吧!昆仑万维Skywork超级智能体,给你的办公桌装上AI最强大脑! 字节跳动炸开AI新边界!开源多模态模型BAGEL:这颗“魔法贝果”有多能打? AI“神医”的开源盛宴?谷歌医疗大模型MedGemma来了! 王炸登场!Claude 4 Opus/Sonnet 全平台深度解析:不止聊天,AI真能‘肝’大项目了?
DeepWiki 开源版本:AI 帮你自动写代码 Wiki,告别手动苦海!重塑AI推理格局?微软Phi-4模型震撼发布:轻量化性能炸裂炸裂!微软这门免费AI Agent新手课,GitHub近2万星,简直是宝藏!ComfyUI“打通任督二脉”:直接调用Veo2、GPT-4o等65大模型!一键串联你的AI工作流AI圈炸锅了!Mistral Medium 3:性能 SOTA,成本打骨折,企业玩家的新宠?字节终于开源“扣子”同款引擎了!FlowGram:AI 时代的可视化工作流利器
java 微服务框架技术Apache ServiceComb Spring MVC核心组件HandlerAdapter 的详解 Mysql锁竞争问题的解决方法 王炸登场!Claude 4 Opus/Sonnet 全平台深度解析:不止聊天,AI真能‘肝’大项目了? Spring 三级缓存能解决什么问题和原理(超详细) 全场景AI革命!DeepSeek官方开源生态工具库「狂飙」指南
标签聚合
java 教程 动态规划 算法 deepseek 设计模式 spring AI

COPYRIGHT © 2023 墨风如雪博客. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

免责声明 - 隐私政策