Spring Boot переходные зависимости @Component с @ConditionalOnBean - PullRequest
0 голосов
/ 11 мая 2018

У меня есть проект Spring Boot 1.5.x, где некоторые @Component зависят от других @Component, и, в конечном счете, по цепочке зависимостей некоторые @Component могут быть включены или полностью отключены с помощью @ConditionalOnProperty.

Я использую @ConditionalOnBean, чтобы избежать создания экземпляра @Component, который зависит от других @Component, которые не были созданы из-за отсутствия properties.

Однако он работает только для прямых зависимостей, а не для транзитивных зависимостей, но я не могу понять, почему.

Позвольте мне попытаться объяснить на простом примере.

Учитывая MyServices.kt:

private val logger = KotlinLogging.logger {}

class MyServices

@ConditionalOnProperty("service.a")
@Service
class ServiceA {
    init {
        logger.info { "A SERVICE" }
    }
}

@ConditionalOnBean(ServiceA::class)
@ConditionalOnProperty("service.b")
@Service
class ServiceB(
        private val serviceA: ServiceA
) {
    init {
        logger.info { "B SERVICE depends on $serviceA" }
    }
}

@ConditionalOnBean(ServiceB::class)
@ConditionalOnProperty("service.c")
@Service
class ServiceC(
        private val serviceB: ServiceB
) {
    init {
        logger.info { "C Service depends on $serviceB" }
    }
}

Со следующим application.yml:

service:
  a: false
  b: true
  c: true

затем Spring при запуске вылетает со следующим:

**************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in org.gotson.transitivebeandependencies.ServiceC required a bean of type 'org.gotson.transitivebeandependencies.ServiceB' that could not be found.


Action:

Consider defining a bean of type 'org.gotson.transitivebeandependencies.ServiceB' in your configuration.

Вот результат автоконфигурации:

Positive matches:

ServiceC matched:
      - @ConditionalOnProperty (service.c) matched (OnPropertyCondition)
      - @ConditionalOnBean (types: org.gotson.transitivebeandependencies.ServiceB; SearchStrategy: all) found bean 'serviceB' (OnBeanCondition)

Negative matches:

ServiceA:
      Did not match:
         - @ConditionalOnProperty (service.a) found different value in property 'service.a' (OnPropertyCondition)

   ServiceB:
      Did not match:
         - @ConditionalOnBean (types: org.gotson.transitivebeandependencies.ServiceA; SearchStrategy: all) did not find any beans (OnBeanCondition)
      Matched:
         - @ConditionalOnProperty (service.b) matched (OnPropertyCondition)

Однако со следующим application.yml:

service:
  a: true
  b: false
  c: true

тогда все работает нормально, создается только экземпляр ServiceA, а бины ServiceB и ServiceC не создаются.


То же самое поведение с @Bean вместо @Component работает как ожидалось.

MyBeans.kt:

private val logger = KotlinLogging.logger {}

@Configuration
class MyBeans {

    @ConditionalOnProperty("bean.a")
    @Bean
    fun beanA(): BeanA {
        logger.info { "A BEAN" }
        return BeanA("beanA")
    }

    @ConditionalOnBean(BeanA::class)
    @ConditionalOnProperty("bean.b")
    @Bean
    fun beanB(beanA: BeanA): BeanB {
        logger.info { "B BEAN depends on $beanA" }
        return BeanB("beanB")
    }

    @ConditionalOnBean(BeanB::class)
    @ConditionalOnProperty("bean.c")
    @Bean
    fun beanC(beanB: BeanB): BeanC {
        logger.info { "C BEAN depends on $beanB" }
        return BeanC("beanC")
    }

}

data class BeanA(val name: String)
data class BeanB(val name: String)
data class BeanC(val name: String)

С application.yml:

bean:
  a: false
  b: true
  c: true

Я не получаю бобов типа BeanA, BeanB или BeanC экземпляров.

Вот результат автоконфигурации:

Negative matches:

MyBeans#beanA:
      Did not match:
         - @ConditionalOnProperty (bean.a) found different value in property 'bean.a' (OnPropertyCondition)

   MyBeans#beanB:
      Did not match:
         - @ConditionalOnBean (types: org.gotson.transitivebeandependencies.BeanA; SearchStrategy: all) did not find any beans (OnBeanCondition)
      Matched:
         - @ConditionalOnProperty (bean.b) matched (OnPropertyCondition)

   MyBeans#beanC:
      Did not match:
         - @ConditionalOnBean (types: org.gotson.transitivebeandependencies.BeanB; SearchStrategy: all) did not find any beans (OnBeanCondition)
      Matched:
         - @ConditionalOnProperty (bean.c) matched (OnPropertyCondition)

Я настроил образец репо с тестами для воспроизведения: https://github.com/gotson/spring-transitive

Ответы [ 2 ]

0 голосов
/ 19 июня 2018

@ConditionalOnBean - это проверка на этапе регистрации bean-компонента, и поэтому он должен иметь представление о том, какие bean-компоненты эффективно доступны в ApplicationContext.Бины могут быть зарегистрированы стандартным способом с обычным @Bean, выставляя тот же тип цели, что и тип возврата метода.У вас также может быть FactoryBean с более сложной логикой, которая может привести к экзотическим настройкам, с которыми мы должны справиться.

Независимо от того, порядок является ключевым.Классы конфигурации должны обрабатываться в заданном порядке, если вы хотите, чтобы сопоставление типов bean-компонентов работало правильно.Если у вас есть класс конфигурации C1, который добавляет компонент A, только если компонент B доступен и указанный компонент добавлен C2, C2 должен запускаться первым .

Spring Boot состоит из двух этапов: сначала мы анализируем все настройки пользователя.Как только это будет сделано, мы проанализируем определения компонентов автоконфигурации.Сами автоконфигурации заказываются (с использованием @AutoConfigureBefore, @AutoConfigureAfter).Таким образом, мы можем гарантировать, что если вы введете @ConditionalOnBean в автоконфигурацию, она будет обработана, как и ожидалось, в отношении конфигурации пользователя.И если вы полагаетесь на что-то, внесенное другой автоконфигурацией, вы можете легко заказать это, используя эти аннотации.

Ваша настройка полностью исключает порядок, поэтому он работает, если порядок правильный, и нет, если нет,В Javadoc @ConditionalOnBean ясно указано, что

настоятельно рекомендуется использовать это условие только для классов автоконфигурации.

Если вы хотите узнать больше, есть 3-часовая университетская сессия, которая охватывает именно эту тему на YouTube .

0 голосов
/ 21 мая 2018

Проблема здесь в вашем определении элементов управления.

Для примера @Service (false, true, true) ваше свойство элемента управления определяет, определена ли служба.ServiceA не определен и не создан.ServiceB определен, но не создан, потому что нет определения ServiceA.ServiceC определен, и попытка строительства.Как указано, создание ServiceC завершается неудачно из-за отсутствия экземпляра ServiceB.

Для примера @Bean (false, true, true) ваше свойство элемента управления определяет, создан ли Bean-компонент.BeanA не создается, BeanB не создается, потому что нет BeanA, а BeanC не создается, потому что нет BeanB.

Для примера @Service (true, false, true): ServiceA определена и созданаServiceB не определен и не создан, ServiceC не определен, но не предпринимается попытка построения, потому что ServiceB не определен.

Методы, украшенные @Bean, создают bean-компонент для управления контейнером Spring на этапе настройки.

Класс Java, украшенный @Component (@Service), будет обнаружен в процессе сканирования компонента и зарегистрирован как компонент bean-компонента Spring.

Поскольку аннотация @Service не создает и не регистрирует компонент так же, как @Bean, возможно, что использование @ ConditionalOnClass вместо @ConditionalOnBean может привести к требуемым результатам,Есть несколько предостережений относительно этой аннотации.

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