当前位置: 代码迷 >> java >> 一段时间后,生产春季启动应用程序将引发NoSuchBeanDefinitionException
  详细解决方案

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

热度:67   发布时间:2023-07-17 20:05:13.0

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

我不为什么会这样,可能是因为您直接使用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方式并不符合最佳实践,这就是为什么您的运行时遇到问题的原因。

因此,您可能有两种解决方案:

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

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

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

  相关解决方案