Через некоторое время производственное весеннее загрузочное приложение создает исключение NoSuchBeanDefinitionException - PullRequest
0 голосов
/ 21 февраля 2019

У нас было приложение средней сложности с пружинной загрузкой 1.5.14 с api rest + 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;
    }
}

После перезапуска приложения весенней загрузки, оно отлично работает в течение первых нескольких дней, даже с CA =CAOrderProcessor.Но затем однажды, когда countryCode = CA, он генерирует исключение NoSuchBeanDefinitionException: не доступен квалифицирующий компонент типа 'FedProvTaxCalculator': ожидается, что по крайней мере 1 компонент будет квалифицирован как кандидат для автоматической передачи.После перезапуска Java-приложения оно снова работает для CA = CAOrderProcessor.

Почему Spring Framework ведет себя так?Заранее спасибо!

Проблема может быть решена с помощью

@Configuration public class TaxCalculatorConfiguration {
    @Bean("onTaxCalculator")
    public ITaxCalculator ontairioTaxCalculator() { ... }
}
public class CAOrderProcessor implements IOrderProcessor {
    @Autowire @Qualifier("onTaxCalculator")
    private ITaxCalculator onTaxCalculator;
}

Использование AutowireCapableBeanFactory - это нормально.Почему он сначала работает, а затем дает сбой и дает сбой только на одном ENV - openshift с минимум 2 модулями?другие ENV работают нормально всегда.Похоже, что пружина сначала ослабляет проверку типа бобов autowire, а затем при определенных условиях проверяет тип бобов.Логическое предположение состоит в том, что определение bean-компонента возвращает тип интерфейса, который может быть проксирован, bean-проводка относится к конкретному типу, прокси-интерфейс не соответствует конкретному типу, что вызывает эту ошибку.Но в этом случае это всегда должно давать ошибку.Если нет, если я не использую кеш или вытесняю кеш, я смогу легко воспроизвести его в любых ENV, но он отлично работает на моем локальном macos + oracle jdk 1.8.Я даже создаю докер-контейнер, основанный на рабочем образе докера openshift, для запуска приложения без кеша, удаления кеша, форсирования YGC и FGC, он тоже отлично работает.

1 Ответ

0 голосов
/ 21 февраля 2019

Почему я так себя не веду, возможно потому, что вы используете 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-компонент на основе аннотацийconfig , что считается плохой практикой.Надеюсь, что это поможет.


Обновление 1 - Ответ на комментарий

Что ж, чтобы выяснить, что не тому человеку понадобится полная копия вашего приложения для отладки/ регистрирует и воспроизводит обычные сценарии использования.Вероятно, невозможно сказать, что не так, просто взглянув на примеры, которые вы предоставили (по крайней мере, для меня).

И я только что отметил, что способ использования AutowireCapableBeanFactory не подходит для лучших практик иВот почему у вас есть проблемы во время выполнения.

Таким образом, у вас, вероятно, есть 2 решения:

  1. Избавьтесь от него и используйте несколько иной подход (возможно, аналогичный тому, который я предлагал ранее).Я считаю, что это только один хороший вариант.Но это решать вам.

  2. Включите пружинные бревна и надейтесь, что вы обнаружите там проблему.Вероятно, нужно включить журналы отладки, как это в вашем log4j.xml (я полагаю, это log4j, но может быть что-то еще):

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...