为什么Spring在一台机器上会出现循环依赖问题而另一台机器上却不会?

42

我在我的环境中运行一个基于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并尝试启动服务器来确定中断构建的提交,直到找到导致问题的提交。

不能为null的"contentItemRepository"就是这个:

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

2
很遗憾 Spring 错误信息只提示 "Error creating bean with name 'yourField'",却没有列出包含该字段的类。如果能提示 "Error creating bean YourClass.yourField" 将会更有帮助。在许多情况下,代码中可能会出现许多同名的 'yourField' 字段;那么是哪一个呢? - user2800708
你能添加完整的堆栈跟踪 + flyway bean配置 + 数据源(bean配置)(包括使用的@Profile@Conditional)吗? - fateddy
哪个类实现了ContentItemRepository? - Jimmy T.
这里还没有足够的信息来完整地了解情况...你能像Jimmy T.要求的那样发布你的ContentItemRepository实现吗?此外,你能否请发布一下你担心的flyway beans?最后一个请求,我保证:我们能获取更多的日志吗?只需搜索“repository”即可,我有一种感觉你的存储库可能无法创建,而日志可能会显示原因。 - wholevinski
10个回答

10

我在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


5

我认为你是正确的。我不明白的是,如何在两个不同的盒子上产生了不同的类路径。Maven使用一致的类路径排序,并被用来构建它。 - user2800708
3
链接已损坏 :( - cambunctious

3
我不明白为什么会发生这种情况,但是我想到了唯一的解决方案:
安装Debian 8,它可以解决问题。在另一个干净的Debian 7安装中尝试过,错误少了一些,但仍然存在。Debian 8的干净安装似乎有效。
我只能得出结论,Java必须调用某些系统库,以某种方式影响Spring依赖项解析的顺序。该库必须在Debian 8中升级,使我与其他开发人员和生产使用的Ubuntu安装保持一致。
我不知道那个库可能是什么……扫描文件系统中的文件并按不同的顺序报告它们的东西?解压缩.jar文件并按不同的顺序报告其内容的东西?
对我来说,我们的代码如此敏感于依赖关系解析和注入的确切顺序,这似乎是错误的。看起来也没有任何东西在我们的代码中应该使它对排序敏感,我们没有做任何疯狂的事情,而且遵循着非常标准的用法模式。
如果你问我,Spring的魔力太多,让整个系统变得复杂。我的其他项目都在DropWizard上,那里的依赖注入是手动编码的,没有任何意外情况。
=== 更新
我将Debian 7升级到8,问题仍然存在。因此,我的关于库版本的假设是错误的。一定是我的环境有问题。
我在该计算机上创建了一个新用户。该用户仍然存在问题。这个计算机似乎对某些东西非常不友好,但我无法找出原因。
我想找到真正的原因并理解它,但我认为我无法再花更多时间来解决它。
总之,Debian 8的干净安装可以解决问题。

我知道你已经停止调查了,但是你是否知道期间可能导致问题的原因? - gillesB
不,但我怀疑那台机器上的Java安装出了问题。清洁环境和重新安装Java解决了这个问题,或者至少让我的机器表现得与其他机器相同。因此,如果您遇到问题,我建议在另一个环境中尝试 - 如果它表现相同,那么可能您有一个真正的Spring依赖关系解决问题。 - user2800708
1
我们的项目在两台Windows电脑上运行正常,但在一台Ubuntu电脑上却无法运行。不过,我们找到了导致问题的类并将其重命名(!)。之后,该项目在所有三个系统上都能够正常工作。 - gillesB
与类加载器中类的顺序有关吗? - user2800708
这个问题的奇怪之处在于它取决于编译和执行环境。在我们的情况下,在我的机器上编译和运行(Ubuntu,Java 1.8.0_144)可以工作,在队友的机器上编译和运行(Mac,Java 1.8.0_144)也可以工作,但是在我们的机器上编译并通过docker在第三台机器上运行(Debian,Java 1.8.0_151),只有Mac编译可以工作,而Ubuntu编译则不行... - Alex Vazquez Fente

3

虽然所有答案都是关注于故障的机器以及如何修复它,但我想指出我们应该思考那些没有出现问题的机器。如果存在循环,DI应该会失败!我们希望在所有环境中都能一致地发生这种情况。

我们遇到了相同的行为,生产环境出现了故障,但是测试、开发和CI环境都很好。这太令人难过了。我们无法创建一个具有这个问题的最小示例。


1
没错,这个问题似乎是非确定性的。实现一个确定性的拓扑排序并不难。例如,所有类都可以按字母顺序完全可排序,这可以用来使拓扑排序变得确定性。 - user2800708
我投票支持这个答案,不是因为它是一个答案,而是因为真正的问题不是如何解决循环依赖关系,而是让所有的环境以相同的方式失败。 - PlickPlick

2
你的仓库接口扩展了什么?你可以查看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> {

我会在我的问题中添加更多信息。 - user2800708

2
今天我们遇到了一个问题,情况几乎完全相同——应用程序由于循环引用而无法启动,显然是在构建Spring Data @Repository实例时发生,而且只有在一个开发人员的机器上出现这个问题。
在我们的情况下,解决方案是重构我们的配置,使一个ApplicationListener从在@Configuration类内部定义的匿名类转变为顶级@Component。
似乎Spring Data存储库以某种不明显的方式保持对这些应用程序侦听器的控制,并且通过将我们的侦听器作为匿名内部类,存储库隐式地维护对其封闭的@Configuration的引用(因此由该@Configuration实例化的bean最终自动装配存储库而检测到依赖关系循环)。
所有描述更改lazy init、更改组件扫描顺序、重新安装操作系统、关闭和再次打开等的答案均仅隐藏而不是真正解决问题,我建议在源头上修复它 :)各种计算机上工作或不工作的原因就是如此,正如@xki所暗示的那样,对象图的构建顺序是不确定的。

稳定的拓扑排序。这就是Spring所需要的。 - user2800708

2

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

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


我可以确认Spring 4仍然具有这种不确定性行为。我通过在错误报告的字段上使用@Lazy注释来解决了一个类似的问题,该问题让我感到疯狂,该字段导致了“Error creating bean with name CLASS: Unsatisfied dependency expressed through field FIELD;”错误。 - Alex Vazquez Fente

0

我不知道为什么这个问题会在一台机器上出现而在另一台机器上没有,但我认为问题最可能是两个bean都使用构造函数注入来相互传递引用,从而创建了无法解决的循环依赖- A需要B进行构造,而B又需要A- 两者都不能在另一个已经被创建的情况下被创建。

如果两个对象都需要对方的引用,则需要在对象已经被创建后通过setter注入来传递它。这个问题的答案与你遇到的问题相关。


2
这不是答案。它在一些机器上可以工作,但在其他机器上无法工作。如果这是构造函数注入,那么它将无论如何都不能在任何机器上工作。 - user2800708

0

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


0

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

在Linux中运行以下命令以查找CPU利用率:

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接