Що таке @Qualifier
Якщо впровадити інтерфейс безпосередньо без уточнення, Spring викине NoUniqueBeanDefinitionException.
🟢 Junior Level
@Qualifier — це анотація Spring Framework, яка усуває неоднозначність під час впровадження залежностей (Dependency Injection), вказуючи точний ідентифікатор або кваліфікатор біна, який потрібно впровадити, якщо в контексті знайдено кілька кандидатів одного типу.
Базовий приклад проблеми та рішення
// Інтерфейс відправлення повідомлень
public interface MessageService { void send(String msg); }
@Component("emailService")
public class EmailService implements MessageService { ... }
@Component("smsService")
public class SmsService implements MessageService { ... }
Якщо впровадити інтерфейс безпосередньо без уточнення, Spring викине NoUniqueBeanDefinitionException.
Використання @Qualifier однозначно вирішує конфлікт:
@Service
public class NotificationManager {
private final MessageService messageService;
// Явно вказуємо, який саме бін потрібно впровадити
public NotificationManager(@Qualifier("emailService") MessageService messageService) {
this.messageService = messageService;
}
}
🟡 Middle Level
Розміщення @Qualifier: Декларація vs Точка впровадження
Анотація @Qualifier може застосовуватися у двох місцях життєвого циклу конфігурації:
- У точці впровадження (Injection Site): над полем, сетером або параметром конструктора:
@Autowired @Qualifier("fastClient") private HttpClient httpClient; - У точці оголошення біна (Declaration Site): над класом або
@Bean-методом. При цьому ім’я кваліфікатора може не збігатися з ім’ям біна!@Configuration public class NetworkConfig { @Bean("primaryHttpClient") // Ім'я біна = primaryHttpClient @Qualifier("fastClient") // Кваліфікатор = fastClient public HttpClient httpClient() { return new FastHttpClient(); } }У точці впровадження
@Qualifier("fastClient")успішно знайде цей бін, навіть незважаючи на те, що ім’я біна в контексті інше.
@Qualifier vs @Primary vs @Resource
Це класичне питання технічних співбесід:
| Анотація | Джерело / Специфікація | Принцип роботи | Пріоритет |
|---|---|---|---|
@Qualifier |
Spring (org.springframework...) |
Пошук byType, потім фільтрація за ім’ям кваліфікатора |
Найвищий (завжди перевизначає @Primary) |
@Primary |
Spring | Позначення біна як вибору за замовчуванням при неоднозначності | Нижче за @Qualifier (спрацьовує, лише якщо кваліфікатор не заданий) |
@Resource(name="...") |
Jakarta EE (jakarta.annotation.Resource) |
Пошук строго за ім’ям біна (byName). Тільки якщо не знайдено — пошук byType |
Альтернативний стандарт Java EE |
🔴 Senior Level
Внутрішній механізм: QualifierAnnotationAutowireCandidateResolver
Перевіркою та зіставленням кваліфікаторів керує системний компонент QualifierAnnotationAutowireCandidateResolver усередині DefaultListableBeanFactory:
- Коли резолвер шукає підходящий бін, викликається метод
checkQualifier(BeanDefinitionHolder bdHolder, Annotation annotation, ...): - Спочатку перевіряється: чи є у дескриптора біна (
BeanDefinition) явно заданий кваліфікатор (AutowireCandidateQualifier), що збігається зі значенням анотації. - Якщо явний кваліфікатор не знайдено, Spring використовує ім’я біна (
beanName) та його псевдоніми (aliases) як неявний дефолтний кваліфікатор. - Перевіряються збіги за мета-анотаціями та їхніми атрибутами.
Кастомні типобезпечні кваліфікатори з атрибутами
Використання рядкових значень (@Qualifier("postgresMaster")) схильне до помилок одруків, які неможливо відловити на етапі компіляції.
Створення мета-кваліфікатора з множинними параметрами:
package com.example.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Qualifier // Мета-кваліфікатор Spring
public @interface DatabaseTarget {
Engine engine();
boolean readOnly() default false;
enum Engine { POSTGRES, CLICKHOUSE, ORACLE }
}
Використання при оголошенні та впровадженні:
@Configuration
public class DataSourceConfig {
@Bean
@DatabaseTarget(engine = DatabaseTarget.Engine.POSTGRES, readOnly = false)
public DataSource postgresMaster() { return new HikariDataSource(); }
@Bean
@DatabaseTarget(engine = DatabaseTarget.Engine.POSTGRES, readOnly = true)
public DataSource postgresReplica() { return new HikariDataSource(); }
}
@Service
public class ReportingService {
private final DataSource readOnlyDs;
// Spring зіставить УСІ атрибути анотації: engine == POSTGRES ТА readOnly == true!
public ReportingService(
@DatabaseTarget(engine = DatabaseTarget.Engine.POSTGRES, readOnly = true) DataSource readOnlyDs) {
this.readOnlyDs = readOnlyDs;
}
}
Перевага Senior-рівня: Повна типобезпечність під час компіляції, автодоповнення enum-значень в IDE та виключення людських помилок в іменуванні.
🎯 Шпаргалка для інтерв’ю
30-секундна відповідь
@Qualifier— анотація Spring для точкового вибору конкретного біна за наявності кількох кандидатів одного типу.
- Порядок розв’язання: Spring спочатку знаходить усі біни потрібного типу (
byType), а потім відбирає тих, чий кваліфікатор або ім’я збігається зі значенням у@Qualifier.@Qualifierмає найвищий пріоритет над@Primary: якщо бін позначений@Primary, але в точці виклику вказано@Qualifier("other"), впроваджено буде саме “other”.- Може встановлюватися як у точці ін’єкції, так і в точці оголошення
@Bean.- Для безпечної архітектури рекомендується створювати кастомні анотації, позначені
@Qualifier, що виключає одруки в рядках та забезпечує сувору перевірку типів.
4 каверзних питання з відповідями
-
У чому принципова відмінність між
@Autowired + @Qualifier("db")та@Resource(name = "db")? Відповідь:@Autowired+@Qualifierздійснює пошук за типом (byType), а потім фільтрує кандидатів за кваліфікатором. Анотація@Resource(зі стандарту Jakarta Annotations) здійснює пошук суворо за унікальним ім’ям біна (byName), і тільки якщо бін з таким ім’ям не знайдений, виконує відкат до пошуку за типом. -
Що станеться, якщо вказати
@Qualifier("foo"), але біна з кваліфікатором “foo” немає, проте є бін з ім’ям “foo”? Відповідь: Бін з ім’ям “foo” буде успішно впроваджений. Spring використовує ім’я біна (beanName) як неявний резервний кваліфікатор за замовчуванням (Fallback Qualifier Matching). -
Чи може один і той самий бін мати кілька різних кваліфікаторів? Відповідь: Так. Це реалізується через оголошення кількох кастомних анотацій, кожна з яких позначена мета-анотацією
@Qualifier. Клас біна позначається всіма цими анотаціями, і його можна впроваджувати за будь-якою з них. -
Як Spring зіставляє кастомні кваліфікатори з кількома полями/атрибутами? Відповідь: Spring зіставляє всі атрибути анотації одночасно. Якщо кастомний кваліфікатор має атрибути
format="json"таversion=2, ін’єкція відбудеться лише в тому випадку, якщо обидва ці атрибути у біна та в точці впровадження повністю ідентичні.
Червоні прапорці (чого категорично не можна говорити)
- ❌ «
@Primaryважливіший, ніж@Qualifier» — груба помилка: явний вибір через@Qualifierзавжди має пріоритет над глобальним дефолтом@Primary. - ❌ «
@Qualifierшукає біни за ім’ям без перевірки типу» — пошук завжди починається з перевірки сумісності типів (byType). - ❌ «
@Qualifierможна використовувати лише над полями з@Autowired» — анотація встановлюється над параметрами конструктора, сетерами і навіть над@Bean-методами. - ❌ «Рядковий
@Qualifierкращий за кастомні анотації» — рядки схильні до одруків і ламаються під час рефакторингу.