一段时间后,生产春季启动应用程序将引发NoSuchBeanDefinitionException

时间:2019-02-21 06:12:48

标签: java spring-boot

我们有一个中等复杂的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,它也可以正常工作。

1 个答案:

答案 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的使用方式并不取决于最佳实践,这就是运行时出现问题的原因。

所以您可能有两种解决方案:

  1. 摆脱它,并使用一些不同的方法(可能与我之前建议的方法类似)。我相信那只是一个不错的选择。但这由您决定。

  2. 启用弹簧日志,并希望在那里能够解决问题。可能需要在您的log4j.xml中启用调试日志(我想它是log4j,但可能是其他东西):

<category name="org.springframework.beans"> <priority value="debug" /> </category>