问题描述
在本地开发中不会发生循环依赖问题,但是在容器场景下,制作成镜像启动后异常出现Bean的循环依赖。
问题原因
开发者在代码中使用构造函数注入来引用依赖的 Bean,这种方式可能导致循环依赖问题。虽然 Spring 框架具备循环依赖的处理机制,但它仅适用于通过 @Resource 或 @Autowired 注解进行的 Setter 方法注入或字段注入。如果开发者使用构造函数注入,当Bean的初始化未发生循环依赖,则启动没问题,对应日常开发中不会有循环依赖问题,但是在一些Docker容器场景,则会偶发抛出循环依赖异常。
深入剖析原理
进一步深入分析为什么本地没有循环依赖问题,但是容器里或服务器里偶发会出现循环依赖问题,问题的根源在于 Spring Boot 在扫描和实例化 Bean 时的顺序并非固定,而是可能受到 Jar 包中文件列表顺序 的影响。
- Jar 包文件列表的顺序
- 在 Java 中,Jar 文件实际上是一个压缩包,内部包含了许多类文件和资源文件。当应用程序运行时,可能需要遍历这些文件。例如,Spring Boot 在启动时需要扫描特定路径下的类和资源,以识别需要创建的 Bean。
- 在 Java 中,可以使用 JarFile.entries() 方法获取 Jar 包中的所有条目。然而,需要注意的是:JarFile.entries() 返回的并非是一个稳定有序的列表。根据 Java 官方文档,entries() 返回一个 Enumeration,但并未保证返回的顺序。
- 不同的平台或工具在打包 Jar 文件时,可能导致文件列表的顺序不同。例如,在 Windows 和 Linux 上生成的 Jar 文件,即使内容完全相同,但内部文件的排列顺序可能不同。
- Spring Boot 的资源匹配逻辑
Spring Boot 使用 PathMatchingResourcePatternResolver 类来处理资源的匹配和加载。其中,doFindPathMatchingJarResources() 方法负责在 Jar 文件中查找与指定模式匹配的资源。
protected Set<Resource> doFindPathMatchingJarResources(Resource rootDirResource, String subPattern) throws IOException {