【问题标题】:After a while, production spring boot app throws NoSuchBeanDefinitionException过了一会儿,生产 spring boot 应用程序抛出 NoSuchBeanDefinitionException
【发布时间】:2019-02-21 06:12:48
【问题描述】:

我们有一个中等复杂的 spring boot 1.5.14 应用程序,后端使用 rest api + mybatis,前端使用 angular 4 和 material/prime-ng。从开发人员的盒子到 UAT 环境,它都可以正常工作,但在生产中,前几天它工作正常,然后抛出 NoSuchBeanDefinition。生产环境为openshift + openjdk版本“1.8.0_171”。

要精简应用并留下相关信息,这里是代码sn-ps:

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 上失败 - openshift 至少有 2 个 pod?其他 ENV 始终可以正常工作。看起来spring最初放松了autowire bean-type检查,后来在某些条件下,它检查了bean-type。逻辑猜测是bean-definition返回接口类型,可能被代理,bean-wiring指的是具体类型,代理接口不等于具体类型,引发此错误。但在这种情况下,它应该总是给出错误。如果没有,如果我不使用缓存或驱逐缓存,我应该能够在任何 ENV 中轻松地重新生成它,但它在我的本地 macos + oracle jdk 1.8 上运行良好。我什至创建了一个基于生产 openshift docker 镜像的 docker 容器来运行没有缓存、驱逐缓存、强制 YGC 和 FGC 的应用程序,它也可以正常工作。

【问题讨论】:

    标签: java spring-boot


    【解决方案1】:

    我不知道为什么会这样,可能是因为您直接使用AutowireCapableBeanFactory,甚至更糟糕的是与@Cacheable结合使用。

    您应该重新考虑您的框架级代码。我相信你永远不应该直接使用AutowireCapableBeanFactory,尤其是在你的情况下。这很简单,您可以使用简单的Mapcountry_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. 启用 spring 日志并希望您能在那里发现问题。可能需要在您的log4j.xml 中启用类似的调试日志(我想它是 log4j,但可能是别的东西):

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

    【讨论】:

    • 实际情况更复杂。将一组字符串类型标准映射到处理器 itfc 是在框架级别,实现由许多业务线提供,每个 LOB 可以提供 0..n impl 以及如何查找属性。要么将字符串类型标准集映射到托管 bean-name,要么按需创建 bean 并将其连接为当前方式,或者使用 if-else 或 map-pre-hold bean 进行多级工厂工厂消除配置。关键是要弄清楚原因
    • 谢谢你,rxn1d。理解。除非你之前写过 spring 或者之前遇到过这个问题,否则不容易回答。
    猜你喜欢
    • 2019-09-16
    • 2016-04-10
    • 2018-10-22
    • 2020-08-30
    • 1970-01-01
    • 2016-10-12
    • 1970-01-01
    • 2017-11-10
    • 1970-01-01
    相关资源
    最近更新 更多