问题描述
问题:
问题出现在运行spring-boot,java 8的k8s pod中(下面有更多详细信息)
使用ObjectProvider<>
并调用*provider.getobject(.....)*
时,
在Spring Configuration中定义的原型bean,时不时地(从不
找到使之定期发生的方法)未调用二传手注入方法。
更新10月2日:,请参见Spring Issue #25840
在大多数情况下,它可以很好地工作,但有时它会构造一个新对象 但没有调用@Autowired setter方法(已检查日志信息)。
我们还发现90%的时间发生在应用程序启动后,但并非总是如此。
更新日期为9月22日:一旦发生,它将一直发生,我的意思是不是一次失败,而下次它正常运行时,它将永远无法调用设置器。
9月23日更新:与真实应用程序中的并发问题相关的新证据直到现在才出现,似乎只有一个线程 产生问题。 (请看下面的课程以更好地理解 此说明)
ToipParentClass (这是一个策略实现)已将@Autowired设置为
- VLocityService
- OrderManagemenService
InternetParentClass (这是一项策略实现)已将@Autowired设置为
- 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映像中(项目中的所有内容),但均未通过失败。
关于在哪里寻找问题的任何想法,关于尝试什么的建议,甚至 一个解决方案:-),将非常欢迎,在此先感谢
在产品版本,类别下面。
有关版本的详细信息:
k8s:
v1.14.9-eks-658790
弹簧靴:
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中找到真实代码)
// ============================================================
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类添加到示例
解决方法
我发现的问题
更新10月1日:请阅读问题中的@Denium评论!!!! (为此)。
在今天(9月23日)更新之后,显然该问题是并发问题,我能够轻松地重现它(请参阅SimpleSpringAppTest.java) 在Simple Spring应用中,没有spring-boot。
使所有Strategy实施都具有相同的@Autowired设置器集 而且错误仍然存在。
似乎已经完成了一些缓存,并且@Autowired setters是从缓存中获取的,而不是从新构造的对象中获取的,尽管我试图在这么短的时间内挖掘难以理解的spring源。
问题已解决,避免了并发(下面的更改),所以现在是我的问题:
这是预期的行为还是错误?
我找不到任何有关此问题的文档或在任何地方描述此行为,因此仍然是我的问题。
我在spring-boot版本1.5.22、2.1.4(我们当前使用的版本)和2.3.4中进行了测试,并且在所有情况下都出现了相同的问题,仅在简单的spring应用程序中,不需要RestController或如此。
解决方法1
添加一个中间工厂,以确保“同步”创建策略bean。
更新十月1:从@Deinum注释开始,据我了解,Spring将每次(或几乎每次)扫描类以查找批注,因此我猜想变通方法2可能更好解决方案。
此解决方案更适合我当前的环境。
新类策略工厂
请注意getStrategy(...)
方法是同步的,我想这个解决方案
将对性能产生一些影响,但仍然无法衡量。
@Component
public class StrategyFactory {
@Autowired
ObjectProvider<Strategy> provider;
public synchronized Strategy getStrategy(final MathOperEnum operation) {
return this.provider.getObject(operation);
}
}
RestController中的更改
现在使用StrategyFactory代替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;
}
}
,
这不是在Strategy
方法中创建Configuration#getStrategy
bean实例的正确方法,因为它不会使用自动装配来调用setter。
Constructor<? extends Strategy> constructor =
selectorEnum.getImplementationClass().getConstructor();
return constructor.newInstance();
使用@Autowired
的方式,看来您想创建一个由Spring处理实例创建的bean。
您可以参考答案https://stackoverflow.com/a/52355649/2614885来使用AutowireCapableBeanFactory
,它会在spring容器中为您指定的bean ID在您的情况下似乎是'mobileKey'或'tvKey'的bean ID中创建bean。>
在您的@Configuration
类中尝试以下代码
@Configuration
public class Configuration {
@Autowired private AutowireCapableBeanFactory autowireCapableBeanFactory;
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Strategy getStrategy(final StrategyEnum selectorEnum) {
try {
Constructor<? extends Strategy> constructor =
selectorEnum.getImplementationClass().getConstructor();
Strategy strategyBean = constructor.newInstance();
autowireCapableBeanFactory.autowireBean(strategyBean);
return strategyBean;
}
catch (Exception ex) {
throw new Exception("Not able to instantiate interface implementation"
+ " for enum: " + selectorEnum,ex);
}
}
}