Spring不调用原型Bean中的@Autowired setter [英] Spring not calling @Autowired setters in Prototype Beans
问题描述
问题出现在运行spring-boot,java 8的k8s pod中(下面有更多详细信息)
The problem appears in a k8s pod running spring-boot, java 8 (more details below)
使用ObjectProvider<>
并调用*provider.getObject(.....)*
时,
在Spring Configuration中定义的原型bean,时不时地(从不
找到使它定期发生的方法)未调用二传手注入方法.
When using ObjectProvider<>
and calling *provider.getObject(.....)*
, on
a prototype bean defined in Spring Configuration, every now and then (never
find a way to make it happen regularly) setter injection methods are not called.
更新10月2日:参见春季问题#25840
在大多数情况下,它可以很好地工作,但是有时,它会构造一个新对象 但没有调用@Autowired setter方法(已检查日志信息).
Most of the time this works perfectly well, but sometimes, it constructs a new object but misses to call the @Autowired setter method (checked with log information).
我们还发现90%的时间发生在应用程序启动后,但并非总是如此.
We also discover that 90% of the time it happens after application startup, but not always.
9月21日更新:删除(重新启动)de pod解决了该问题.
UPDATE sept, 21: Deleting (Restarting) de pod solves the problem.
更新日期为9月22日:一旦发生,它将一直发生,我的意思是不是一次失败,而下次它运行正常时,它将始终无法调用设置方法.
UPDATE sept, 22: Once it happens it will happen all the time, I mean is not that it fails once, and the next time it works ok, it will always fail to call setters.
9月23日更新:与真实应用程序中的并发问题有关的新证据直到现在都没有出现,似乎只有一个线程 产生问题. (请看下面的课程,以更好地理解 此说明)
UPDATE sept, 23: New evidence related to concurrency problem in the real application, didn't appear until now, a single thread alone seemed to generate the problem. (take a look at classes below to understand better this description)
ToipParentClass (这是一个策略实现)已将@Autowired设置为
ToipParentClass (this is a Strategy Implementation) has setter @Autowired for
- VlocityService
- OrderManagemenService
InternetParentClass (这是一项策略实施)已将@Autowired设置为
InternetParentClass (this is a Strategy Implementation) has setter @Autowired for
- VlocityService
- OrderManagemenService
日志(注释)
[-nio-80-exec-10] GuidTaskController : Build Strategy using XOM_TOIP_OFFER .
[p-nio-80-exec-2] GuidTaskController : Build Strategy using XOM_INTERNET_OFFER .
[-nio-80-exec-10] ToipParentClass : @Constructing ToipParentClass
[p-nio-80-exec-2] InternetParentClass : @Constructing InternetParentClass
[-nio-80-exec-10] ToipParentClass : @Autowiring VlocityServices@7951cd46
[p-nio-80-exec-2] InternetParentClass : @Autowiring VlocityServices@7951cd46
[-nio-80-exec-10] ToipParentClass : @Autowiring OrderManagementService@3385326a
-------------------------------------------------------------
ERROR !!! Missing @Autowiring log
[p-nio-80-exec-2] InternetParentClass : @Autowiring OrderManagementService@7951cd46
-------------------------------------------------------------
[p-nio-80-exec-2] Controller : Unexpected Error
2020-09-22T18:56:45.544525916Z
-------------------------------------------------------------
ERROR: NullPointer when using not set OrderManagementService
-------------------------------------------------------------
2020-09-22T18:56:45.544530395Z java.lang.NullPointerException: null
2020-09-22T18:56:45.544534074Z at InternetParentClass.generateIds(InternetParentClass.java:50) ~[classes!/:BUG001_PrototypeBeanAutowired-8]
2020-09-22T18:56:45.544538568Z at GuidTaskController.setGUID(GuidTaskController.java:180) ~[classes!/:BUG001_PrototypeBeanAutowired-8]
我在 https://github.com/toniocus/strategy-calculator以单独的,不同的jdk8版本运行它,并在pod中使用的同一docker映像(项目中的所有内容)中运行,但未能使其失败.
I made a simple test in https://github.com/toniocus/strategy-calculator run it standalone, different jdk8 versions, also in the same docker image used in the pod (all things in the project), and failed to make it FAIL.
关于在哪里寻找问题的任何想法,关于尝试什么的建议,甚至 一个解决方案:-),将非常欢迎,在此先感谢
Any ideas on where to look for a problem, suggestions on what to try, or even a solution :-), will be greatly welcome, thanks in advance
在产品版本,类别下面.
Below the product versions, classes.
k8s:
v1.14.9-eks-658790
spring-boot:
2.1.4.RELEASE
JDK:
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (IcedTea 3.12.0) (Alpine 8.212.04-r0)
OpenJDK 64-Bit Server VM (build 25.212-b04, mixed mode)
课程
所涉及的类的代码段,没有真实的代码,不用担心语法错误. (可以在 https://github.com/toniocus/strategy-calculator 中找到真实代码)
The Classes
A snippet on the classes involved, no real code, do not worry about syntax errors. (real code can be found in https://github.com/toniocus/strategy-calculator)
// ============================================================
public interface Strategy {
void work(...);
}
// ============================================================
@Service
public class Service {
public void doSomething() {
......
......
}
}
// ============================================================
public class MobileStrategy implements Strategy {
private Service service;
@Autowired
public void setService(Service s) {
this.service = s; // setter not called every now and then
}
public void work(...) {
this.service.doSomething(); // throws NullPointerException every now an then
}
}
// ============================================================
public enum StrategyEnum {
MOBILE("mobileKey", MobileStrategy.class),
TV("tvKey", TvStrategy.class),
.......
public Class<Strategy> getImplementationClass() {
......
}
public StrategyEnum findByKey(String key) {
.....
}
}
// ============================================================
@Configuration
public class Configuration {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Strategy getStrategy(final StrategyEnum selectorEnum) {
try {
Constructor<? extends Strategy> constructor =
selectorEnum.getImplementationClass().getConstructor();
return constructor.newInstance();
}
catch (Exception ex) {
throw new Exception("Not able to instantiate interface implementation"
+ " for enum: " + selectorEnum
, ex);
}
}
}
// ============================================================
@RestController
public class MathOperRestController {
@Autowired
ObjectProvider<Strategy> provider;
@GetMapping("/{operation}/{x}/{y}")
public BigDecimal add(
@PathVariable("operation") final String operation
, @PathVariable("x") final BigDecimal x
, @PathVariable("y") final BigDecimal y
) {
Strategy strategy = this.provider.getObject(StrategyEnum.findByKey(operation));
strategy.doWork(x, y);
return BigDecimal.ZERO;
}
9月21日更新将Service类添加到示例
UPDATE sept,21 added Service class to samples
推荐答案
我发现的问题
更新10月1日:请阅读问题中的@Denium评论!!!! (为此).
The problem I found
Update oct, 1: Please read @Denium comment in the question !!!! (thanks for it).
在今天(9月23日)更新之后,显然该问题是并发问题,我能够轻松地重现它(请参阅
After today's (Sept, 23) update, seems clearly the problem is a concurrency problem, I was able to reproduce it easily (See SimpleSpringAppTest.java) in a Simple Spring app, no spring-boot.
使所有Strategy实施都具有相同的@Autowired设置器集 而且错误仍然存在.
Made all Strategy implementations have the same set of @Autowired setters and the error is still there.
似乎已经完成了一些缓存,并且@Autowired设置器是从缓存中获取的,而不是从新构造的对象中获取的,尽管我试图在这么短的时间内挖掘难以理解的弹簧源.
Seems there is some caching done, and @Autowired setters are taken from the cache, and not from the newly constructed object, although I try to dig into spring sources difficult to understand in so short time.
问题已解决,避免了并发(下面的更改),所以现在是我的问题:
The problem was solved, avoiding concurrency (changes below), so now my question:
这是预期的行为还是错误?
我找不到任何有关此问题的文档或在任何地方描述此行为,因此仍然是我的问题.
I was not able to find any documentation regarding this problem or describing this behaviour anywhere, so still a question for me.
我在spring-boot版本1.5.22、2.1.4(我们当前使用的版本)和2.3.4中进行了测试,并且在所有情况下都出现了相同的问题,仅在一个简单的spring应用程序中,不需要RestController或如此.
I tested in spring-boot versions 1.5.22, 2.1.4 (what we are currently using), and 2.3.4 and in all cases the same problem appears, just in a simple spring application, no need of RestController or so.
添加一个中间工厂,以确保同步"创建策略bean.
Add an intermediate Factory to ensure Strategy beans are created 'synchronously'.
更新十月1:从@Deinum注释开始,据我了解,Spring将每次(或几乎每次)扫描类以查找批注,因此我猜想变通方法2可能更好解决方案.
Update oct, 1: After @Deinum comments, from what I understood, Spring will be scanning classes every time (or almost every time) for annotations, so I guess Workaround 2 is probably a better solution.
此解决方案更适合我当前的环境.
This solution is more suitable for my current environment.
请注意getStrategy(...)
方法是同步的,我想这个解决方案
将对性能产生一些影响,但仍然无法衡量.
Note the getStrategy(...)
method is synchronized, I guess this solution
will have some performance impact, but still not able to measure it.
@Component
public class StrategyFactory {
@Autowired
ObjectProvider<Strategy> provider;
public synchronized Strategy getStrategy(final MathOperEnum operation) {
return this.provider.getObject(operation);
}
}
RestController中的更改
现在使用StrategyFactory代替ObjectProvider
Changes in RestController
Now using the StrategyFactory instead of the ObjectProvider
@RestController
public class MathOperRestController {
@Autowired
StrategyFactory factory;
@GetMapping("/{operation}/{x}/{y}")
public BigDecimal add(
@PathVariable("operation") final String operation
, @PathVariable("x") final BigDecimal x
, @PathVariable("y") final BigDecimal y
) {
Strategy strategy = this.factory
.getStrategy(StrategyEnum.findByKey(operation));
strategy.doWork(x, y);
return BigDecimal.ZERO;
}
}
解决方法2
- 使StrategyFactory ApplicationContextAware
- 在每个策略实施中添加@ Componente/@ Scope批注
- 删除@Configuration类
@Component
public class StrategyFactory implements ApplicationContextAware {
private ApplicationContext ctx;
public Strategy getStrategy(final StrategyEnum operation) {
return this.ctx.getBean(
operation.getImplementationClass()
);
}
@Override
public void setApplicationContext(
final ApplicationContext applicationContext)
throws BeansException {
this.ctx = applicationContext;
}
}
这篇关于Spring不调用原型Bean中的@Autowired setter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!