问题描述
我们有一个中等复杂的Spring Boot 1.5.14应用程序,后端使用api + mybatis,前端为4角,材质/ prime-ng。 从开发人员的盒子到UAT环境,它都可以正常工作,但是在生产中,它在头几天工作良好,然后抛出NoSuchBeanDefinition。 生产环境为openshift + openjdk版本“ 1.8.0_171”。
要精简应用程序并保留相关信息,请参见以下代码段:
public interface ITaxCalculator {
BigDecimal calc(BigDecimal amount);
}
public class FedProvTaxCalculator implements ITaxCalculator {
... ...
}
@Configuration
public class TaxCalculatorConfiguration {
...
@Bean("onTaxCalculator")
public ITaxCalculator ontairioTaxCalculator() {
FedProvTaxCalculator ret = ..
...
return ret;
}
@Bean("bcTaxCalculator")
public ITaxCalculator britishColumbiaTaxCalculator() {
FedProvTaxCalculator ret = ..
...
return ret;
}
}
public class CAOrderProcessor implements IOrderProcessor {
@Autowire @Qualifier("onTaxCalculator")
private FedProvTaxCalculator onTaxCalculator;
@Autowire @Qualifier("bcTaxCalculator")
private FedProvTaxCalculator bcTaxCalculator;
....
}
// --------------- below code are at framework level -----
public interface IOrderProcessor {
void process(Order order);
}
public interface IOrderProcessorFactory {
IOrderProcessor createOrderProcessor(String countryCode, MembershipType membership);
}
@Service
public class OrderProcessorFactoryPropImpl implements IOrderProcessorFactory {
@Autowired
private AutowireCapableBeanFactory beanFactory;
@Override
@Cacheable("orderProcessor")
public IOrderProcessor createOrderProcessor(String countryCode, MembershipType membership) {
String clzName = resolveOrderProcessClzName(countryCode, membership); // resolve to CAOrderProcess clz-name
try {
Object ret = Class.forName(clzName).newInstance();
beanFactory.autowireBean(ret);
// the above line throws error after a while
return (IOrderProcessor)ret;
} catch (Exception ex) {
...
throw new RuntimeException(...);
}
}
private String resolveOrderProcessClzName(String countryCode, MembershipType membership) {
String clzName = lookupFromPropFile(countryCode + "." + membership.name());
if (StringUtils.isBlank( clzName )) {
clzName = lookupFromPropFile(countryCode);
}
return clzName;
}
}
重新启动Spring Boot应用程序后,即使使用CA = CAOrderProcessor,它也可以在前几天正常工作。 但是有一天,使用countryCode = CA时,它抛出NoSuchBeanDefinitionException:没有可用的'FedProvTaxCalculator'类型的合格Bean:期望至少有1个有资格作为自动装配候选者的bean。 重新启动Java应用程序后,它可以再次用于CA = CAOrderProcessor。
Spring框架为什么会这样? 提前致谢!
这个问题可以通过解决
@Configuration public class TaxCalculatorConfiguration {
@Bean("onTaxCalculator")
public ITaxCalculator ontairioTaxCalculator() { ... }
}
public class CAOrderProcessor implements IOrderProcessor {
@Autowire @Qualifier("onTaxCalculator")
private ITaxCalculator onTaxCalculator;
}
使用AutowireCapableBeanFactory很好。 为什么它先开始工作,然后失败,然后仅在一个ENV上失败-带有最少2个吊舱的openshift? 其他ENV总是可以正常工作。 看起来像Spring最初放松了autowire bean类型的检查,然后在某些条件下,它检查了bean类型。 逻辑猜测是bean定义返回接口类型,该接口类型可能是代理类型,bean接线是具体类型,代理接口不等于具体类型,从而引发此错误。 但是在那种情况下,它应该总是出错。 如果没有,如果我不使用高速缓存或逐出高速缓存,我应该能够轻松地在任何ENV中重现它,但是它在我的本地macos + oracle jdk 1.8上可以正常工作。 我什至基于生产openshift docker镜像创建了一个docker容器来运行应用程序而无需缓存,逐出缓存,强制YGC和FGC,它也可以正常工作。
1楼
我不为什么会这样,可能是因为您直接使用AutowireCapableBeanFactory
,甚至更糟地是与@Cacheable
结合使用。
您应该重新考虑 框架级别的代码 。
我认为您绝对不应直接使用AutowireCapableBeanFactory
,尤其是在您的情况下。
这很简单,您可以通过使用country_code + membershi_type -> processor
简单Map
来轻松实现相同的结果,例如:
@Configuration
public class ProcessorConfiguration {
. . .
@Bean("cAOrderProcessor ")
public IOrderProcessor cAOrderProcessor() [
return new CAOrderProcessor();
}
. . .
@Bean
public IOrderProcessorFactory processorFactory() {
// create country_code + membershi_type -> processor map
Map<ProcessorKey, IOrderProcessor> processorMap = new HashMap<>();
// not sure about values in MembershipType, so I put SOME just for example
// this map also can be a bean if you're gonna need that in other parts of app
processorMap.put(new ProcessorKey("CA", MembershipType.SOME), cAOrderProcessor());
// set it to factory
return new OrderProcessorFactoryPropImpl(processorMap );
}
. . .
}
public class OrderProcessorFactoryPropImpl implements IOrderProcessorFactory {
private final Map<ProcessorKey, IOrderProcessor> processorMap;
public OrderProcessorFactoryPropImpl(Map<ProcessorKey, IOrderProcessor> processorMap) {
this.processorMap = processorMap;
}
@Override
// @Cacheable("orderProcessor") you dont need that because get it from map costs nothing
// changed the name to "get" instead of "create"
public IOrderProcessor getOrderProcessor(String countryCode, MembershipType membership) {
// just get processor by key
return processorMap.get(constructKey(countryCode, membership));
}
private ProcessorKey constructKey(String countryCode, MembershipType membership) {
return new ProcessorKey(countryCode, membership);
}
}
我还注意到,您将Java和基于注释的bean配置混合使用,这被认为是不好的做法。 希望这会有所帮助。
更新1-回答评论
好吧,要弄清楚出什么问题了,人们将需要完整复制您的应用程序以进行调试/记录并重现其通常的用例。 仅查看您提供的示例(至少对我而言),就不可能说出问题所在。
我只是指出您使用AutowireCapableBeanFactory
方式并不符合最佳实践,这就是为什么您的运行时遇到问题的原因。
因此,您可能有两种解决方案:
摆脱它,并使用一些不同的方法(可能与我之前建议的方法类似)。 我相信那只是一个不错的选择。 但这由您决定。
启用spring日志,并希望在那里能够解决问题。 可能需要在
log4j.xml
启用类似的调试日志(我想它是log4j,但可能是其他东西):
<category name="org.springframework.beans"> <priority value="debug" /> </category>