我们有一个中等复杂的Spring Boot 1.5.14应用程序,其中后端使用了api + mybatis,前端使用了angular 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个合格的autowire候选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,它也可以正常工作。
答案 0 :(得分:0)
我不知道为什么会这样,可能是因为您直接使用AutowireCapableBeanFactory
,甚至更糟糕的是与@Cacheable
结合使用。
您应该重新考虑您的框架级别代码。我认为您绝对不应直接使用AutowireCapableBeanFactory
,尤其是在您的情况下。这很简单,您可以花费更少的精力并使用Map
中的简单country_code + membershi_type -> processor
来获得相同的结果,例如:
@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
的使用方式并不取决于最佳实践,这就是运行时出现问题的原因。
所以您可能有两种解决方案:
摆脱它,并使用一些不同的方法(可能与我之前建议的方法类似)。我相信那只是一个不错的选择。但这由您决定。
启用弹簧日志,并希望在那里能够解决问题。可能需要在您的log4j.xml
中启用调试日志(我想它是log4j,但可能是其他东西):
<category name="org.springframework.beans">
<priority value="debug" />
</category>