РУКОВОДСТВА ПО ПРОГРАММИРОВАНИЮ
Как исправить ошибку зависимости Spring: «ожидался один соответствующий bean-компонент, но найдено X»
От новичка до продвинутого, узнайте 5 способов исправить эту ошибку
TL;DR
- Назначить компонент
@Primary
- Будьте точны с
@Qualifier
- Принятие нескольких bean-компонентов
- Явная конфигурация компонента
- (Дополнительно) переключение функций
Примеры кодов: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
: реализация SearchServiceGoogleSearch
: реализация SearchServiceSearchController
: потребитель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 и найдите прекрасную работу