РУКОВОДСТВА ПО ПРОГРАММИРОВАНИЮ

Как исправить ошибку зависимости Spring: «ожидался один соответствующий bean-компонент, но найдено X»

От новичка до продвинутого, узнайте 5 способов исправить эту ошибку

TL;DR

  1. Назначить компонент @Primary
  2. Будьте точны с @Qualifier
  3. Принятие нескольких bean-компонентов
  4. Явная конфигурация компонента
  5. (Дополнительно) переключение функций

Примеры кодов:https://github.com/geraldnguyen/sample-spring-core/tree/dependency/multiple-beans

Ошибка: «ожидался один соответствующий bean-компонент, но найдено X»

Проверьте ветку dependency/multiple-beans с https://github.com/geraldnguyen/sample-spring-core, чтобы продолжить

У нас есть простая настройка приложения загрузки Spring:

  • SearchService: интерфейс для операций поиска
  • BingSearch: реализация SearchService
  • GoogleSearch: реализация SearchService
  • SearchController: потребитель SearchService и удобная точка входа для нашего тестирования.
// SearchService: the interface for search operations
public interface SearchService {
    String[] search(String query);
}

// BingSearch: an implementation of SearchService
@Service
public class BingSearch implements SearchService {
    @Override
    public String[] search(String query) {
        return new String[]{ "Bing sample result" };
    }
}

// GoogleSearch: an implementation of SearchService
@Service
public class GoogleSearch implements SearchService {
    @Override
    public String[] search(String query) {
        return new String[]{ "Google sample result" };
    }
}

// SearchController: a consumer of SearchService and a convenient entry point for our testing
@RestController
@RequestMapping("/search/")
public class SearchController {
    @Autowired
    private SearchService searchService;

    @GetMapping
    public String[] search(@RequestParam("query") String query) {
        return searchService.search(query);
    }
}

При запуске приложения вы столкнетесь с ошибкой UnsatisfiedDependencyException: Error creating bean with name ‘searchController’: Unsatisfied dependency expressed through field ‘searchService’: No qualifying bean of type ‘nguyen.gerald.samples.spring.core.service.search.SearchService’ available: expected single matching bean but found 2: bingSearch,googleSearch

023-02-02T23:40:32.430+08:00  WARN 42400 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'searchController': Unsatisfied dependency expressed through field 'searchService': No qualifying bean of type 'nguyen.gerald.samples.spring.core.service.search.SearchService' available: expected single matching bean but found 2: bingSearch,googleSearch
2023-02-02T23:40:32.436+08:00  INFO 42400 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-02-02T23:40:32.457+08:00  INFO 42400 --- [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2023-02-02T23:40:32.481+08:00 ERROR 42400 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

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

Description:

Field searchService in nguyen.gerald.samples.spring.core.api.SearchController required a single bean, but 2 were found:
 - bingSearch: defined in file [C:\Users\huy_n\workspace\samples\spring\core\target\classes\nguyen\gerald\samples\spring\core\service\search\BingSearch.class]
 - googleSearch: defined in file [C:\Users\huy_n\workspace\samples\spring\core\target\classes\nguyen\gerald\samples\spring\core\service\search\GoogleSearch.class]


Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Disconnected from the target VM, address: '127.0.0.1:62797', transport: 'socket'

Process finished with exit code 1te

Поскольку и GoogleSearch, и BingSearch реализуют SearchService, Spring не смог определить, какую реализацию следует вернуть в SearchController, поэтому выдает указанную выше ошибку.

Решение № 1. Назначьте основной компонент

Ознакомьтесь с веткой dependency/multiple-beans-solution-primaryот https://github.com/geraldnguyen/sample-spring-core, чтобы следовать дальше. Вы также можете просмотреть изменения здесь

Это самое простое решение. Мы можем обозначить любую реализацию, например. GoogleSearch в качестве основной реализации SearchService путем применения аннотации @Primary к реализации, т. е. GoogleSearch, класса

@Primary  // <-- this is the only change
@Service
public class GoogleSearch implements SearchService {
    @Override
    public String[] search(String query) {
        return new String[]{ "Google sample result" };
    }
}

Запустим наше приложение. Ошибки больше не должно быть, и мы можем получить доступ к имитированному результату поиска Google по адресу http://localhost:8080/search/?query=hello.

Решение № 2. Будьте конкретны с аннотацией @Qualifier

Ознакомьтесь с ответвлением dependency/multiple-beans-solution-qualifier от https://github.com/geraldnguyen/sample-spring-core, чтобы продолжить. Вы также можете просмотреть изменения здесь

Это так же просто. Мы можем указать, какой SearchService использовать, применив аннотацию @Qualifier к свойству зависимости (или параметру, если вы используете внедрение конструктора)

@RestController
@RequestMapping("/search/")
public class SearchController {
    @Autowired
    @Qualifier("bingSearch")      // <-- this is the only change
    private SearchService searchService;

    @GetMapping
    public String[] search(@RequestParam("query") String query) {
        return searchService.search(query);
    }
}

Запустим наше приложение. Ошибки больше не должно быть, и мы можем получить доступ к фиктивному результату поиска Bing по адресу http://localhost:8080/search/?query=hello.

Решение № 3 — Принятие нескольких бинов

Ознакомьтесь с веткой dependency/multiple-beans-accepting-multipleот https://github.com/geraldnguyen/sample-spring-core, чтобы следовать дальше. Вы также можете просмотреть изменения здесь

SearchController может принимать несколько компонентов и использовать их по своему усмотрению. В следующем примере он выбирает bean-компонент с индексом 0 для использования

@RestController
@RequestMapping("/search/")
public class SearchController {
    @Autowired
    private SearchService[] searchServices; // <-- accepting multiple

    @GetMapping
    public String[] search(@RequestParam("query") String query) {
        return searchServices[0].search(query);  // <-- pick the first one to use
    }
}

Это решение больше подходит для случаев использования агрегации, когда, например, API отправляет запрос пользователя в несколько поисковых систем и объединяет их отдельные ответы, прежде чем вернуть их пользователю.

Решение № 4 — Явная конфигурация компонента

Проверьте ветку dependency/multiple-beans-configurationот https://github.com/geraldnguyen/sample-spring-core, чтобы следовать дальше. Вы также можете просмотреть изменения здесь

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

Содержание SearchConfig может быть таким тривиальным, как следующее:

@Configuration
public class SearchConfig {
    @Autowired
    private GoogleSearch googleSearch;

    @Autowired
    private BingSearch bingSearch;

    @Bean
    public SearchService searchService() {
        return googleSearch;
    }
}

Или мы можем добавить возможность включения/выключения переключателя, чтобы сделать наше приложение более гибким и более управляемым извне. Несмотря на то, что мы определяем свойство google-search.enabled в application.properties, его не нужно объявлять так, потому что Springboot поддерживает несколько режимов внешних конфигураций.

// SearchConfig.java
@Configuration
public class SearchConfig {
    @Autowired
    private GoogleSearch googleSearch;

    @Autowired
    private BingSearch bingSearch;

    @Bean
    public SearchService searchService(@Value("${google-search.enabled:false}") boolean enableGoogleSearch) {
        return enableGoogleSearch ? googleSearch : bingSearch;
    }
}

// application.properties
google-search.enabled=true

Запустим наше приложение. Мы можем получить доступ к фиктивным результатам поиска Google по адресу http://localhost:8080/search/?query=hello.

(Расширенное) Решение № 5 — переключение функций

Проверьте ветку dependency/multiple-beans-feature-switchот https://github.com/geraldnguyen/sample-spring-core, чтобы следовать дальше. Вы также можете просмотреть изменения здесь

Предыдущее решение хорошо работает, если нам нужно выбрать только Google или Bing. Но он не будет масштабироваться, когда у нас будет 3 или более вариантов. Переключение функций — гораздо лучшее решение.

Пусть YahooSearch примут участие в конкурсе

// YahooSearch.java
@Service
public class YahooSearch implements SearchService {
    @Override
    public String[] search(String query) {
        return new String[]{ "Yahoo sample result" };
    }
}

Для реализации переключения функций требуется всего 3 шага:

  • Мы определим перечисление SearchProvider с именами всех поддерживаемых поставщиков поиска.
  • Затем настраиваем search.provider=<a name>в application.properties (или один из поддерживаемых режимов внешние конфигурации).
  • Затем мы считываем настроенное значение search.provider в качестве параметра в нашей конфигурации @Bean и возвращаем поисковую систему, соответствующую этому параметру.
// SearchConfig.java
@Configuration
public class SearchConfig {
    enum SearchProvider { GOOGLE, BING, YAHOO }
    @Autowired
    private GoogleSearch googleSearch;

    @Autowired
    private BingSearch bingSearch;

    @Autowired
    private YahooSearch yahooSearch;

    @Bean
    public SearchService searchService(@Value("${search.provider:BING}") SearchProvider provider) {
        return switch (provider) {
            case BING -> bingSearch;
            case YAHOO -> yahooSearch;
            default -> googleSearch;
        };
    }
}


// application.properties
search.provider=YAHOO

Запустим наше приложение. Мы можем получить доступ к фиктивным результатам поиска Yahoo по адресу http://localhost:8080/search/?query=hello.

Заключение

В этой статье мы узнали 5 различных способов устранения ошибки «ожидаемый-один-соответствующий-бин-но-найден-несколько» с примерами кода и простыми шагами.

Если вам понравился этот урок, подпишитесь, чтобы получать мою последнюю статью на свой адрес электронной почты. Спасибо.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу