英文:
Spring circular reference error only happens some of the time, and @Lazy has no effect
问题
我之前询问过关于Spring循环引用问题的问题,但我从中没有获得有用的信息。我将重新陈述当前的问题并提供额外的信息。
我正在使用Spring 5.0.9(也尝试过5.2.5)和Spring Boot 2.0.5。
当我们将代码合并到主分支时,相同代码的构建会在四台不同的开发服务器上启动。经常情况下,有时仅在一台服务器上,有时在两台服务器上,我们会遇到如下错误:
UnsatisfiedDependencyException:创建bean时出错,bean名称为'billPreferenceRestService':通过字段'componentService'表达的不满足的依赖关系;嵌套异常是org.springframework.beans.factory.UnsatisfiedDependencyException:创建bean时出错,bean名称为'stateManagerOperation':通过字段'addressServiceClient'表达的不满足的依赖关系;嵌套异常是org.springframework.beans.factory.BeanCurrentlyInCreationException:创建bean时出错,bean名称为'addressServiceClient':bean名称为'addressServiceClient'的bean已注入到其他bean[addressInfoServiceImpl]中,作为循环引用的一部分,但最终已被包装。
这个问题不会每次都发生在同一台服务器上。不变的是,如果我们简单地要求在该服务器上重新运行构建,错误就不会发生。
我已经花了很长时间查看bean引用,重点关注addressServiceClient
bean,它似乎总是与此有关,但我找不到循环引用,但可能有几层深,这使得很难找到。
在我们的笔记本上进行正常测试时,我们从未遇到过这个错误,它只会在构建服务器上发生。但是,我能够提取服务器上构建的fat jar,并将其下载到我的笔记本电脑上,在命令行上执行,它确实会显示相同的错误。我还在没有显示错误的构建上做了同样的操作,但在笔记本电脑上运行时却没有显示错误。如果我将“错误”的jar与“良好”的jar进行比较,我只会在存档条目的顺序中看到差异。
由于AddressServiceClient
似乎总是与这个所谓的循环有关,我决定在类声明中添加@Lazy
。但这没有任何效果。
在那之后,我设置了这个应用,以便如果传入了任何参数(通常不会提供命令行参数),则不会对应用程序进行正常启动,而是执行以下循环(从实际代码中略有改编):
for (BeanDefinition bd:scanner.findCandidateComponents(basePackage)) {
String beanClassName = bd.getBeanClassName();
System.out.println("cn[" + beanClassName + "] lazy [" + bd.isLazyInit()+ "] dependsOn [" + bd.getDependsOn()+ "]");
}
请注意,如果执行了该代码,它不会执行正常的启动代码,大致如下:
getSpringApplicationBuilder()。sources(Application.class)。run(args);
其中Application
具有@SpringBootApplication
注解。
我在我的笔记本电脑上执行了“错误”的jar,并使用虚拟命令行参数执行了此代码。我在输出中搜索了AddressServiceClient
类。lazy
标志为false
(而dependsOn
为null)。
我想lazy
标志不是true
,因为没有在SpringApplicationBuilder
上执行run()
,所以跳过了一些bean解析步骤。我可能需要确认这一点。
请注意,我们的应用程序类都没有使用构造函数注入。我们使用的一些平台库(由另一个团队编写)中的一些构造函数正在使用构造函数注入,但我检查了每一个这样的构造函数,它们要么注入简单的属性,要么注入绝对不可能在我们的循环中的对象。
我们没有使用AOP。
我已经考虑了一些想法。不要费心建议我进行重构。如果我不知道需要重构什么来解决这个问题,那是没有意义的。整个应用程序相当大。
英文:
I asked a recent question about Spring circular reference issues, but I didn't get anything useful from that. I'm going to restate the current problem with additional information.
I'm using Spring 5.0.9 (also tried 5.2.5) and Spring Boot 2.0.5.
When we merge code to master, a build of the same code starts on four different dev servers. Very often, sometimes on only one server, sometimes on two, we get an error like the following:
UnsatisfiedDependencyException: Error creating bean with name 'billPreferenceRestService': Unsatisfied dependency expressed through field 'componentService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'stateManagerOperation': Unsatisfied dependency expressed through field 'addressServiceClient'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'addressServiceClient': Bean with name 'addressServiceClient' has been injected into other beans [addressInfoServiceImpl] in its raw version as part of a circular reference, but has eventually been wrapped.
It doesn't happen on the same server each time. Invariably, if we simply request the build to be rerun on that server, the error doesn't happen.
I have spent a long time looking at bean references, centering on the addressServiceClient
bean, which seems to always be part of this, and I cannot find a circular reference, but it could be several levels deep, which makes it very hard to find.
We never see this error with normal testing on our laptops, it only ever happens on the build server. However, I was able to extract the fat jar that is constructed on the server and download it to my laptop, and when I execute that from the command line, it does demonstrate the same error. I've also done the same with the fat jar on builds that do NOT demonstrate the error, and it does not show the error running on the laptop. If I compare the "bad" jar with the "good" jar, I only see differences in the order of entries in the archive.
Since the AddressServiceClient
always seems to be involved in this alleged cycle, I decided to add @Lazy
to the class declaration. This had no effect.
After that, I set up the application so that if any parameters were passed in (normally no command-line parameters are supplied), instead of doing a normal startup of the application, it would instead execute the following loop (paraphrased somewhat from the real code):
for (BeanDefinition bd : scanner.findCandidateComponents(basePackage)) {
String beanClassName = bd.getBeanClassName();
System.out.println("cn[" + beanClassName + "] lazy[" + bd.isLazyInit() + "] dependsOn[" + bd.getDependsOn() + "]");
}
Note that if it executes that code, it doesn't execute the normal startup code, which is approximately this:
getSpringApplicationBuilder().sources(Application.class).run(args);
Where Application
has the @SpringBootApplication
annotation.
I executed the "bad" jar on my laptop with a dummy command-line argument so it would execute this code. I searched through the output for the AddressServiceClient
class. The lazy
flag was false
(and dependsOn
was null).
I suppose the the lazy
flag isn't true
because by not executing run()
on the SpringApplicationBuilder
, I skipped some bean resolution steps. I could use confirmation of that.
Note that none of our application classes are using constructor injection. Some of the constructors in some of the platform libraries we use (written by another team) are using constructor injection, but I examined each one of those, and they're either injecting simple properties or objects that couldn't possibly be in our cycle.
We are not using AOP.
I could some ideas. Don't bother suggesting that I refactor. That is pointless if I don't know what I need to refactor to solve this problem. The overall application is quite large.
专注分享java语言的经验与见解,让所有开发者获益!
评论