为啥 Spring 在一台机器上而不是另一台机器上出现循环依赖问题?

Posted

技术标签:

【中文标题】为啥 Spring 在一台机器上而不是另一台机器上出现循环依赖问题?【英文标题】:Why does Spring get circular dependency issues on one machine and not another?为什么 Spring 在一台机器上而不是另一台机器上出现循环依赖问题? 【发布时间】:2015-06-03 13:23:15 【问题描述】:

在我的环境中运行基于 Spring Data 的应用程序时遇到问题。我正在运行 Debian,但我的同事使用的是 Mac 或 Ubuntu。我的环境变量中没有任何特殊设置,并且使用的 Java 版本与其他人完全相同。

我在日志中看到了这个,提示是循环引用问题导致实例化失败:

nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'flyway.CONFIGURATION_PROPERTIES':
Initialization of bean failed;
...
nested exception is
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'flyway': Requested bean is currently in
creation: Is there an unresolvable circular reference?

所以问题似乎是flyway需要一些依赖,他们需要flyway。

问题是,为什么这只发生在我的环境而不是其他任何人?即使在内存中使用 H2 的测试中,我也看到了问题,所以不是我的数据库有问题。

是否可能是 Spring 自动装配以某种方式混淆,并尝试以错误的顺序执行操作,从而在尝试设置存储库时为空?

Spring 是否有一个糟糕的拓扑排序来排序依赖项?

为什么它会在我的环境中出现异常行为?

类路径的顺序会影响其行为吗?

=======================

应用程序将无法启动并出现此错误:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contentItemRepository': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Repository interface must not be null on initialization!
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:175)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:127)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1517)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:251)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1127)

=============================

ContentItemRepository 签名是:

@Repository
@Transactional
public interface ContentItemRepository extends JpaRepository<ContentItem, String>, JpaSpecificationExecutor<ContentItem> 

=============================

这曾经对我有用,我能够通过迭代所有提交、执行 mvn clean install 并尝试启动服务器来识别破坏构建的提交,直到找到破坏它的增量.

不能为空的'contentItemRepository'就是这个:

@Component
+public class UrlAliasRequestConverter implements Mapper<UrlAliasRequest, UrlAlias> 
+
+    /**
+     * The content item contentItemType repository.
+     */
+    @Autowired
+    private ContentItemRepository contentItemRepository;

【问题讨论】:

很遗憾 Spring 错误说“创建名称为 'yourField' 的 bean 时出错”,但没有列出包含该字段的类。当然,“创建 bean YourClass.yourField 时出错”是可能的,而且更有帮助。在许多情况下,相同的字段名称“yourField”可能会在代码中出现多次;哪一个? 能否添加完整的堆栈跟踪 + flyway bean-config + 数据源 bean-config(包括使用的 @Profiles 或 @Conditionals)? 什么类实现了 ContentItemRepository? 这里还不够全面……您能像 Jimmy T. 所问的那样发布您的 ContentItemRepository 实现吗?另外,你能把你担心的飞路豆贴出来吗? 最后请求,我保证:我们也能获得更多的日志吗?只需将它们梳理为“存储库”;我感觉您的存储库可能创建失败,日志可能会显示原因。 【参考方案1】:

您的存储库接口在扩展什么?可以看一下Spring源码,看看抛出异常的原因:

https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/repository/core/support/RepositoryFactoryBeanSupport.java

@SuppressWarnings("unchecked")
public Class<? extends T> getObjectType() 
    return (Class<? extends T>) (null == repositoryInterface ? Repository.class : repositoryInterface);

这是我的存储库的示例:

@Repository
public interface GameRepository extends JpaRepository<Game, Long> 

【讨论】:

我会在我的问题中添加更多信息。【参考方案2】:

我不知道为什么这个特定问题会发生在一台机器上而不是另一台机器上,但我认为这个问题很可能是两个 bean 都使用构造函数注入来相互传递引用,这会创建无法解析的循环依赖 - A需要 B 来构建它,而后者又需要 A——如果没有另一个已经被创建,则两者都不能被创建。 如果两个对象都需要对另一个的引用,则需要在创建对象后通过 setter 注入传递它。 this question 的答案与您遇到的问题有关。

【讨论】:

这不是答案。它确实适用于某些机器而不适用于其他机器。如果是构造函数注入,无论如何它都不会在任何机器上工作。【参考方案3】:

我不明白为什么会这样,但这是我想出的唯一解决方案:

安装 Debian 8,它就可以工作了。

我在另一个 Debian 7 的全新安装中尝试过,那里的错误更少,但仍然有一些。全新安装 Debian 8 似乎可以正常工作。

我只能得出结论,Java 一定是在调用某个系统库,这在某种程度上影响了 Spring 依赖项的解析顺序。该库必须在 Debian 8 中升级,使我与其他开发和生产正在使用的 Ubuntu 安装保持一致。

我不知道那个库可能是什么......它扫描文件系统中的文件,以不同的顺序报告它们?解压 .jar 文件并以不同顺序报告其内容的东西?

在我看来,我们的代码对依赖解析和注入的确切顺序如此敏感,这似乎是错误的,但情况似乎确实如此。看起来我们的代码中没有任何内容应该使其对排序敏感,我们没有做任何疯狂的事情,并且遵循非常标准的使用模式。

如果你问我,太多的春天魔法让纸牌屋保持不变。我的其他项目在 DropWizard 上,并且依赖注入是手动编码的,所以不足为奇。

=== 更新

我将 Debian 7 box 升级到 8,问题依旧。因此,我关于它是库版本的假设是错误的。一定与我的环境有关。

我在盒子上创建了一个新用户。该用户的问题仍然存在。这个盒子有一些东西它真的不喜欢,但我不知道它是什么。

我想找到真正的原因并理解它,但我认为我真的不能再花时间去弄清楚它了。

无论如何,全新安装 Debian 8 可以解决问题。

【讨论】:

我知道您已停止调查,但您是否知道问题的原因? 不,但我怀疑那台机器上的 Java 安装不知何故搞砸了。一个干净的环境和新安装的 Java 修复了它,或者至少使我的机器与其他机器的行为方式相同。因此,如果您遇到问题,我建议您尝试另一个环境 - 如果它的行为相同,那么您可能遇到了真正的 Spring 依赖解决问题。 我们的项目在 2 台装有 Windows 的 PC 上工作,但在装有 Ubuntu 的 PC 上却没有。无论如何,我们找到了导致问题的类并重命名(!)它。之后,该项目再次在所有三个系统上运行。 与类加载器中类的顺序有关吗? 这个问题的奇怪之处在于它依赖于编译和执行环境。在我们的例子中,在我的机器上编译和运行(Ubuntu,Java 1.8.0_144)可以工作,在队友的机器(Mac,Java 1.8.0_144)上编译和运行也可以,但是在我们的机器上编译并在第三台机器上运行通过docker (Debian, Java 1.8.0_151) 仅适用于 Mac 编译,不适用于 Ubuntu 编译...【参考方案4】:

这很可能与类文件在行中读取的顺序有关

dir.listFiles()PathMatchingResourcePatternResolver.doRetrieveMatchingFiles()

由于文件列表(类文件)的顺序取决于平台,并且没有对数组进行排序,因此加载类的顺序取决于平台如何返回它们。

参考(存档):http://forum.spring.io/forum/spring-projects/container/115998-circular-dependency-identification-inconsistent

【讨论】:

我认为你是对的。我不明白的是类路径在两个不同的盒子上是如何不同的。 Maven 使用一致的类路径排序,并用于构建它。 链接已损坏 :(【参考方案5】:

我在 Ubuntu 16.04 上遇到了同样的问题。

我发现问题与

@ComponentScan(basePackages = "com.my.app")

代码正在运行至少 5 台不同的机器(windows、ubuntu 15.04 和 ubuntu 16.04 桌面),但没有启动我们的 CI 服务器(ubuntu 16.04 服务器)。

我改变之后

@ComponentScan(basePackages = "com.my.app")

@ComponentScan(basePackages = "com.my.app.service", "com.my.app.config", "com.my.app")

代码也在 CI 服务器上运行。

我认为这是 bean 加载器的 Spring 问题...

更新:

https://github.com/spring-projects/spring-boot/issues/6045

https://jira.spring.io/browse/SPR-14307

【讨论】:

【参考方案6】:

我昨天遇到了同样的问题。不知道为什么,但我找到了解决问题的方法:将循环依赖树中涉及的所有bean标记为lazy-init。不仅是那些直接循环相互依赖的,还有所有引用它们的bean!

该项目是旧项目,因此它只使用spring 3。我不确定spring的更高版本是否仍然存在这个问题。

【讨论】:

我可以确认 Spring 4 仍然具有这种不确定的行为。我通过在错误 Error creating bean with name CLASS: Unsatisfied dependency expressed through field FIELD; 报告的字段上使用 @Lazy 注释修复了一个让我发疯的类似问题【参考方案7】:

我们今天遇到了这个问题,情况几乎相同 - 应用程序由于循环引用而无法启动,显然是在构造 Spring Data @Repository 实例时,并且仅在一个开发人员的机器上。

在我们的例子中,解决方案是重构我们的配置,以便将 ApplicationListener&lt;BeforeSaveEvent&gt; 从定义在 @Configuration 类中的匿名类移到*** @Component

似乎 Spring Data 存储库以某种非显而易见的方式为这些应用程序侦听器保留了某种句柄,并且通过将我们的作为匿名内部类,存储库隐式维护对其封闭 @Configuration 的引用(因此由于通过 @Configuration 实例化的 bean 最终自动装配了存储库,因此检测到依赖循环。

所有描述更改惰性初始化、更改组件扫描顺序、重新安装操作系统、将其关闭再打开等的答案都只是隐藏而不是真正解决问题,我建议改为从源头修复它:) 事情在各种机器上工作或不工作的原因仅仅是,正如@xki 所暗示的,对象图构造顺序是不确定的。

【讨论】:

稳定的拓扑排序。这就是 Spring 所需要的。【参考方案8】:

对我们来说,问题是 CPU 使用率高,而且这个错误突然出现。

在linux中运行以下命令来查看cpu利用率:

top -b -n1 | grep ^%Cpu | awk 'cpu+=$9ENDprint "Current CPU Utilization is : " 100-cpu/NR'

如果它是 100 或接近,这意味着我们必须杀死一些其他当前未使用的微服务或升级我们的系统容量。

【讨论】:

【参考方案9】:

虽然所有答案都集中在发生故障的机器以及如何修复它,但我想指出,我们应该对没有发生故障的机器感到好奇。如果有循环,DI应该会失败!我们希望这在所有环境中始终如一地发生。

我们遇到了生产失败但暂存、开发和 CI 都很好的相同行为。那太可悲了。我们无法针对该问题创建一个最小示例。

【讨论】:

确实,问题似乎是不确定的。实现确定性的拓扑排序并不难。例如,所有类始终按字母顺序完全排序,这可用于确定***排序。 我之所以选择这个答案,不是因为它是一个答案,而是因为真正的问题不是绕过循环依赖,而是让所有 env 都以同样的方式失败【参考方案10】:

临时拐杖解决方案:ENV spring.main.lazy-initialization=true

【讨论】:

以上是关于为啥 Spring 在一台机器上而不是另一台机器上出现循环依赖问题?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 django sqlite3 数据库在一台机器上与另一台机器上的工作方式不同?

在一台机器上出现 DacServices 错误,而不是在另一台机器上

如何在一台机器上保存张量板投影仪检查点文件并在另一台机器上打开?

javax.net.ssl.SSLHandshakeException 在一台机器上,但不是在另一台机器上

C++:Cookie 不存储在一台机器上,而是存储在另一台机器上

php 在一台机器上解析失败,但在另一台机器上解析失败