Что происходит при инъекции по типу в Spring, если есть два бина одного типа с разной реализацией?
Что проверяют
Знаете ли вы, что происходит на старте контекста, и умеете ли перечислить способы разрешения неоднозначности по убыванию явности. Вопрос базовый, но по нему хорошо видно, читал ли кандидат хоть раз стек ошибки Spring.
Как отвечать
При инъекции по типу контейнер ищет кандидатов по типу. Если подходит больше одного бина и выбрать нельзя, контекст не поднимается: бросается NoUniqueBeanDefinitionException, обёрнутый в UnsatisfiedDependencyException с указанием точки внедрения. Это ошибка старта, а не рантайма.
Кандидата контейнер выбирает по порядку:
- бин, помеченный
@Primary(двух таких быть не должно, иначе снова ошибка); - приоритет из аннотации
@Priority, где меньшее значение важнее; - совпадение имени точки внедрения с именем бина.
Явный способ разрулить:
@Autowired
public OrderService(@Qualifier("smsNotifier") Notifier notifier) { ... }Рядом стоит упомянуть собственную квалифицирующую аннотацию, если реализации выбираются по смыслу, а не по имени.
Отдельно назовите вариант, который часто и имеют в виду: если нужны все реализации, их внедряют списком List<Notifier> или картой Map<String, Notifier>, где ключ это имя бина, а порядок задаётся через @Order. Так развилка уходит из кода в конфигурацию.
Где спотыкаются
Говорят, что приложение упадёт при первом вызове. Падает старт контекста, и это удобно: ошибка видна сразу, а не под нагрузкой.
Второе: полагаются на резолв по имени, не зная о нём. Код работает, потому что имя параметра совпало с именем бина, а после безобидного переименования параметра приложение перестаёт стартовать. Совпадение имени это запасной механизм, а не замена @Qualifier.
Третье: вешают @Primary на первую попавшуюся реализацию, лишь бы контейнер перестал ругаться. Вторая реализация после этого не вызывается нигде, и ошибку конфигурации замечают уже в проде. @Primary уместен там, где есть осмысленный вариант по умолчанию, который иногда переопределяют.