@ConditionalOnMissingBean и @ConditionalOnBean для одного и того же типа

#java #spring #spring-boot

#java #весна #весенняя загрузка #spring

Вопрос:

У меня странная ситуация. Я пытаюсь написать класс автоматической настройки, в котором я создаю компонент, если другой компонент существует или нет, поэтому у меня есть условие для компонента и условие для отсутствующего компонента для того же типа..

Вот мой код:

 @Bean
@ConditionalOnMissingBean(
    type = {"com.example.Api"}
)
public ApiManager apiManager() {
    return new ApiManager() {
        public Result getValue(Request request) {
            throw new Exception("Api not provided.");
        }

        public Result setValue(Request request) {
            throw new Exception("Api not provided.");
        }
    };
}

@Bean
@ConditionalOnBean(
    type = {"com.example.Api"}
)
public ApiManager apiManager(Api api) {
    return new ApiManagerImpl(api);
}
  

Проблема в том, что он не проверяет, @ConditionalOnBean если он уже проверил, @ConditionalOnMissingBean что компонент типа com.example.Api не отсутствует, а затем компонент не создается.

И я получаю ошибку типа:

 Parameter 2 of constructor in com.example.ServiceImpl required a bean of type 'com.example.ApiManager' that could not be found.
- Bean method 'apiManager' in 'ApiAutoConfiguration' not loaded because @ConditionalOnMissingBean (types: com.example.Api; SearchStrategy: all) found bean 'api'
- Bean method 'apiManager' in 'ApiAutoConfiguration' not loaded because @ConditionalOnMissingBean (types: com.example.Api; SearchStrategy: all) found bean 'api'
  

Ответ №1:

То, что вы получаете, вполне логично. Условия оцениваются по порядку, поэтому автоконфигурации обрабатываются после настройки пользователем.

Если бы контекст выполнял то, что вы ожидаете, нам пришлось бы оценить все условия всех определений компонентов, прежде чем что-либо делать. И это сведет на нет цель автоматической настройки, обрабатывающей тот факт, что компонент определенного типа был предоставлен пользователем (user config) или автоматической конфигурацией.

Вам нужно переписать эту автоматическую конфигурацию, чтобы импортировать вещи по порядку. Один из способов сделать это — переместить каждый метод в класс частной конфигурации пакета, а затем выполнить следующее:

 @Configuration
@Import({ApiPresentConfiguration.class, ApiNotPresentConfiguration.class})
  

Лучший способ справиться с этим на самом деле — вообще не делать этого и просто использовать ObjectProvider :

 @Bean
public ApiManager apiManager(ObjectProvider<Api> api) {
    // Check if API is present and then return the appropriate implementation
}
  

Комментарии:

1. Я ожидал, что он будет работать с ObjectProvider , но все еще не работает с использованием условных обозначений… При использовании с @Import должен ли я использовать внутренние ApiPresentConfiguration условные обозначения?

2. Импорт — это просто упорядочивание того, как вы хотите, чтобы условия оценивались.

3.Используя импорт, он вводится, ApiNotPresentConfiguration но Api компонент создается другим, AutoConfiguration поэтому порядок неправильный, да? Api AutoConfiguration Должно быть выполнено перед ApiMenagerAutoConfiguration да?

Ответ №2:

попробуйте использовать @Primary .

Я не уверен, как это будет соответствовать вашему варианту использования, но рассмотрите следующее:

 @Bean
public ApiManager apiManager() {
    return new ApiManager() {
        public Result getValue(Request request) {
            throw new Exception("Api not provided.");
        }

        public Result setValue(Request request) {
            throw new Exception("Api not provided.");
        }
    };
}

@Bean
@ConditionalOnBean(
    type = {"com.example.Api"}
)
@Primary
public ApiManager apiManager(Api api) {
    return new ApiManagerImpl(api);
}
  

Случай 1: Api не существует

  1. Первый компонент действует как компонент по умолчанию и используется.
  2. Второй компонент вообще не создается.

Случай 2: Api существует

  1. Создается первый компонент (поскольку он используется по умолчанию), но не используется.
  2. Создается второй компонент, и в итоге вы получаете 2 компонента типа ApiManager ; но поскольку у него есть @Primary ; он будет использоваться.