【问题】容器部署场景Spring Bean偶尔循环依赖问题

news2024/9/20 18:35:59

问题描述

在本地开发中不会发生循环依赖问题,但是在容器场景下,制作成镜像启动后异常出现Bean的循环依赖。

问题原因

开发者在代码中使用构造函数注入来引用依赖的 Bean,这种方式可能导致循环依赖问题。虽然 Spring 框架具备循环依赖的处理机制,但它仅适用于通过 @Resource 或 @Autowired 注解进行的 Setter 方法注入或字段注入。如果开发者使用构造函数注入,当Bean的初始化未发生循环依赖,则启动没问题,对应日常开发中不会有循环依赖问题,但是在一些Docker容器场景,则会偶发抛出循环依赖异常。

深入剖析原理

在这里插入图片描述

进一步深入分析为什么本地没有循环依赖问题,但是容器里或服务器里偶发会出现循环依赖问题,问题的根源在于 Spring Boot 在扫描和实例化 Bean 时的顺序并非固定,而是可能受到 Jar 包中文件列表顺序 的影响。

  1. Jar 包文件列表的顺序
  • 在 Java 中,Jar 文件实际上是一个压缩包,内部包含了许多类文件和资源文件。当应用程序运行时,可能需要遍历这些文件。例如,Spring Boot 在启动时需要扫描特定路径下的类和资源,以识别需要创建的 Bean。
  • 在 Java 中,可以使用 JarFile.entries() 方法获取 Jar 包中的所有条目。然而,需要注意的是:JarFile.entries() 返回的并非是一个稳定有序的列表。根据 Java 官方文档,entries() 返回一个 Enumeration,但并未保证返回的顺序
  • 不同的平台或工具在打包 Jar 文件时,可能导致文件列表的顺序不同。例如,在 Windows 和 Linux 上生成的 Jar 文件,即使内容完全相同,但内部文件的排列顺序可能不同。
  1. Spring Boot 的资源匹配逻辑
    Spring Boot 使用 PathMatchingResourcePatternResolver 类来处理资源的匹配和加载。其中,doFindPathMatchingJarResources() 方法负责在 Jar 文件中查找与指定模式匹配的资源。
protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern) throws IOException {
    ...
    JarFile jarFile = ((JarURLConnection) rootDirURL.openConnection()).getJarFile();
    Enumeration<JarEntry> entries = jarFile.entries();
    while (entries.hasMoreElements()) {
        JarEntry entry = entries.nextElement();
        String entryPath = entry.getName();
        // 资源匹配逻辑
        if (matcher.match(subPattern, entryPath)) {
            // 处理匹配的资源
        }
    }
    ...
}

如上所示,jarFile.entries() 返回的文件列表顺序不定。这意味着,Spring Boot 在扫描资源并识别 Bean 时,可能因为文件顺序的不同而导致 Bean 的加载顺序发生变化。

  1. Bean 加载顺序对循环依赖的影响
    在大多数情况下,Bean 的加载顺序并不会影响应用程序的启动。然而,当存在循环依赖时,Bean 的加载顺序可能决定了 Spring 能否成功地解决这种依赖关系。

举个例子,假设存在两个 Bean:BeanA 和 BeanB,它们互相依赖。如果 BeanA 先被加载,Spring 可能能够通过代理或其他机制解决依赖。然而,如果 BeanB 先被加载,可能就会导致无法解决的循环依赖,进而抛出异常。

由于 Jar 包中文件列表的顺序不定,导致 Bean 的加载顺序在不同的环境或不同的启动中可能有所不同,这解释了为什么循环依赖问题会 偶尔 发生。

参考链接

解决方案

方案1:干掉循环依赖

最根本的解决方案是 重新设计 Bean 的依赖关系,避免循环依赖的出现。

开发规范:默认禁用构造器注入的循环依赖
PS:在升级SpringBoot3.0后,对应Spring6.0 开始,默认情况下不再允许通过构造器注入的方式解决循环依赖。如果两个 Bean 之间通过构造器注入存在循环依赖,Spring 将会直接抛出 BeanCurrentlyInCreationException,而不再试图通过懒加载代理等方式来解决这个问题。

使用 @DependsOn 注解

Spring 提供了 @DependsOn 注解,允许我们显式地指定 Bean 的加载顺序。

@Component
@DependsOn("beanB")
public class BeanA {
    // ...
}

@Component
public class BeanB {
    // ...
}

通过这种方式,可以确保 BeanB 在 BeanA 之前被初始化。然而,需要谨慎使用该注解,避免引入新的依赖问题。

代码中定义某个Bean延迟加载
@Component
public class BeanA {
    private final BeanB beanB;

    public BeanA(@Lazy BeanB beanB) {
        this.beanB = beanB;
    }
    // ...
}

方案3:启用延迟加载

spring.main.lazy-initialization=true 是 Spring Boot 应用中的一个配置选项,它用于启用 延迟初始化功能。
在 Spring Boot 2.2 及以上版本中,lazy-initialization 的默认值是 false。这意味着默认情况下,Spring Boot 应用中的所有 Bean 都是在应用启动时立即初始化的,而不是在第一次使用时才进行初始化。

延迟初始化 (Lazy Initialization) 的概念

在 Spring 应用中,默认情况下,所有的 @Bean 和组件(如 @Component, @Service, @Repository 等)在应用启动时都会被立即创建和初始化。这意味着在应用程序启动时,所有这些 Bean 都会被加载到 Spring 应用上下文中,无论它们何时在应用的生命周期中被使用。

启用延迟初始化 (lazy initialization) 后,**只有在第一次需要使用某个 Bean 的时候,该 Bean 才会被创建和初始化。**这可以加快应用启动的速度,尤其是在有许多不需要立即初始化的 Bean 时。

延迟初始化的优缺点

优点

  • 减少应用启动时间:对于大型应用来说,减少不必要的 Bean 初始化可以显著提高启动速度。
  • 资源节约:只有在需要时才会创建 Bean,节省了内存和 CPU 资源。

缺点

  • 潜在的延迟:由于 Bean 在第一次使用时才会被创建,这可能导致在应用运行过程中首次调用某个服务时出现轻微的延迟。
  • 调试复杂度:延迟初始化可能会导致某些问题(如配置错误、Bean 的依赖问题)直到运行时才暴露出来,这可能增加调试的复杂性。

注意事项

  • 延迟初始化适用于不需要立即加载的服务和组件,但对于关键服务(如启动时需要立即使用的 Bean),你可能希望保持默认的非延迟加载方式。
  • 延迟初始化可以通过在特定 Bean 上使用 @Lazy(false) 来排除那些需要立即初始化的 Bean。
启用延迟初始化的场景

开发环境:加快开发过程中应用的启动速度,减少等待时间。
测试环境:在单元测试时,只加载特定的 Bean,而不是所有的 Bean,减少测试的开销。
微服务:在某些微服务架构中,可能希望某些 Bean 仅在真正需要时才加载,以节省资源。

如何启用延迟初始化

可以在 application.properties 或 application.yml 中配置 spring.main.lazy-initialization=true 来启用延迟初始化。
application.properties 示例

spring.main.lazy-initialization=true

application.yml 示例

spring:
  main:
    lazy-initialization: true

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038335.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

thinkphp漏洞之sql注入漏洞-builder处漏洞

目录 适用版本 环境搭建 文件下载安装 配置文件修改 漏洞分析 适用版本 注&#xff1a;thinkphp版本&#xff1a;5.0.13<ThinkPHP<5.0.15 、 5.1.0<ThinkPHP<5.1.5 环境搭建 文件下载安装 在github上面下载相应版本&#xff0c;下载think文件&#xff0c;…

乳制品企业怎么防止信息泄露?使用加密软件保障数据安全

乳,制品行业使用加密软件主要是为了保障企业的核心数据安全&#xff0c;防止敏感信息泄露&#xff0c;如:产品配方、生产流程、销售数据等&#xff0c;通过使用加密软件&#xff0c;来保护重要的数据.。一、加密软件在乳制品企业的重要性1、保护重要数据:乳制品企业拥有大量敏感…

SQL 变量写法、排序问题 <12>

一、定义变量排序 目的1&#xff1a;合并学生表和分数表&#xff0c;将每个班分别排名 目的2&#xff1a;遇到相同分数&#xff0c;考虑还是不考虑相同分数排名 学生表&#xff08;1000条&#xff09;和分数表&#xff08;6000条&#xff09;分别如下 1、定义变量、简答排序…

datax和datax-web打包成docker运行

概述 datax和datax-web从一台机器迁移到另一台时&#xff0c;要重新搭建一套运行环境&#xff0c;比较麻烦&#xff1b;打包成docker镜像后迁移就方便多了; 因为我的mysql版本是8&#xff0c;需要在datax的read和write中手动添加8的jdbc驱动 所以我先各自下载好了datax和data…

JavaEE 的入门

1. 学习JavaEE Java EE(Java Platform Enterprise Edition), Java 平台企业版. 是JavaSE的扩展, ⽤于解决企业级的开 发需求, 所以也可以称之为是⼀组⽤于企业开发的Java技术标准. 所以, 学习JavaEE主要是学习Java在 企业中如何应⽤. 前⾯学习的是Java基础, JavaEE 主要学习Jav…

修改docker数据存储目录及拉取镜像安装oracle19c

一、修改docker数据目录&#xff0c;默认安装路径为/var/lib/docker 查看docker主路径 docker info |grep "Docker Root Dir"1.停服务 systemctl stop docker2.新建目录并授权 mkdir /data/docker -p chown -R root:docker /data/docker/3.修改配置文件 原有{}内…

微信小程序预览PDF、H5预览PDF、网页预览PDF,并添加专属文字水印

下载PDF.js 点击PDF.js下载地址 引入预览PDF 文件 // const url new URL("./1.pdf", import.meta.url).href // 在本地项目获取pdf // const url "https://xxxx/05d833041f.pdf" // 在线上链接获取pdf const url query.get(url) // 在地址栏获取pdf c…

2024年Q2震撼来袭!AMD数据中心与笔记本CPU市场独占鳌头,强劲表现引爆业界关注!

根据CPU市场追踪机构Mercury Research的最新报告&#xff0c;AMD在2024年第二季度再次取得了显著成绩&#xff0c;在数据中心和笔记本电脑CPU市场上份额有所增加。然而&#xff0c;Intel在台式机市场仍占据优势&#xff0c;并在整体出货量上保持领先地位。 在2024年第二季度&a…

本地查看的Git远程仓库分支与远程仓库分支数量不一致

说明&#xff1a;一次&#xff0c;在IDEA中想切换到某分支&#xff0c;但是查看Remote没有找到要切换的分支&#xff0c;但是打开GitLab&#xff0c;查看远程仓库&#xff0c;是有这个分支的。 解决&#xff1a;1&#xff09;在IDEA的Git中&#xff0c;点下面Fatch获取一下远程…

Grype:用于容器镜像、文件系统的开源漏洞扫描程序

容器镜像和文件系统的漏洞扫描器 Grype 是一个开源漏洞扫描器&#xff0c;专为容器镜像和文件系统设计&#xff0c;可与强大的软件物料清单 (SBOM) 工具 Syft 无缝集成。 扫描容器镜像或文件系统的内容以查找已知漏洞。 查找主要操作系统软件包的漏洞 Alpine Amazon Linux B…

计算机毕业设计 招生宣传管理系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

Linux基础入门---Centos安装步骤

&#x1f600;前言 本篇博文是关于Centos的详细安装&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f609…

20240814 每日AI必读资讯

号称全球最强AI程序员 “Genie” 横空出世&#xff0c;击败Devin与GPT-4! - Genie在SWE-Bench测试中得分高达30.08%&#xff0c;成为全球最强AI程序员。 - 采用特殊数据集和自我改进机制&#xff0c;使Genie在复杂编码中表现出色。 - 目前已开放申请试用&#xff0c;未来将推…

Android12 SystemUI QS面板新增截屏功能

问题:Android12 中SystemUI版本,QS下拉快捷面板式没有截屏功能的。 需求:客户要求在项目中实现下拉快捷面板具备一键截屏功能 目前自己只针对Android12 mtk/RK平台实践过,接触的全志平台暂未实验验证。 文章目录 前言一、实际实现效果二、修改点1.新增文件2.修改文件三、基…

Redis缓存——缓存更新策略和常见的缓存问题

一.什么是缓存&#xff1f; 前言&#xff1a;什么是缓存? 缓存(Cache),就是数据交换的缓冲区,俗称的缓存就是缓冲区内的数据,一般从数据库中获取,存储于本地代码 前言&#xff1a;为什么要使用缓存&#xff1f; 一句话:因为速度快,好用 缓存数据存储于代码中,而代码运行在内存…

初识C++ · C++11(3)

前言&#xff1a; 本文介绍的是包装器以及线程库的简单了解&#xff0c;但是呢&#xff0c;线程是基于对Linux有一定的了解&#xff0c;所以本文就是简单介绍一下&#xff0c;介绍完包装器以及线程库的简单理解之后C11的特性就到此为止&#xff0c;当然C11远不止于此&#xff…

Pixel Adventure Unity2D开发完整指南

本文参考&#xff1a;2-2. Get and Setup Assets_哔哩哔哩_bilibili 1、下载资源 在Asset Store中下载Pix Adventure1 2的资源&#xff1a; 在import的时候&#xff0c;不用到Scene import进来&#xff0c;如下图所示&#xff0c;Scenes目录反勾选一下。 两个资源都下载完成后…

朱利亚集合和曼德布洛特集合及其图像

朱利亚集合和曼德布洛特集合及其图像 朱利亚集合&#xff08;Julia Set&#xff09;和 曼德布洛特集合&#xff08;Mandelbrot Set&#xff09;除了数学理论上的意义&#xff0c;所生成的分形图像&#xff0c;因其独特的几何美感和无限的复杂性&#xff0c;还被广泛应用于计算机…

增强现实系列—深入探索ARKit:平面检测、三维模型放置与增强现实交互

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

技术研究:Redis 实现消息队列

综述 我们先看看消息队列的消息存取到底有哪些需求吧&#xff1a; 需求1&#xff1a;消息保序&#xff1a;由于消费者是异步处理消息&#xff0c;但是消费者需要按照生产者发送消息的顺序来处理消息&#xff0c;避免后发送的消息被先处理了。 需求2&#xff1a;重复消息处理&…