Какой бин выберет Spring при @Autowired интерфейсе, если есть две его реализации?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разрешение неоднозначности при внедрении зависимостей
Проблема: несколько реализаций одного интерфейса
Когда в Spring контейнере есть несколько бинов, реализующих один и тот же интерфейс, и вы пытаетесь внедрить этот интерфейс через @Autowired, возникает неоднозначность. Spring не знает, какой именно бин выбрать. По умолчанию это приводит к исключению NoUniqueBeanDefinitionException.
Пример проблемы
public interface PaymentService {
void processPayment(double amount);
}
@Component
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Обработка платежа по кредитной карте: " + amount);
}
}
@Component
public class PayPalPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Обработка платежа через PayPal: " + amount);
}
}
// Это вызовет ошибку!
@Service
public class OrderService {
@Autowired
private PaymentService paymentService; // Какой выбрать?
}
Решение 1: @Primary
Отметь один бин как первичный (primary) через аннотацию @Primary. Spring выберет этот бин по умолчанию:
@Component
@Primary // Этот бин будет выбран!
public class CreditCardPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Обработка платежа по кредитной карте: " + amount);
}
}
@Component
public class PayPalPaymentService implements PaymentService {
@Override
public void processPayment(double amount) {
System.out.println("Обработка платежа через PayPal: " + amount);
}
}
// Теперь работает!
@Service
public class OrderService {
@Autowired
private PaymentService paymentService; // Выберет CreditCardPaymentService
}
Решение 2: @Qualifier
Используй аннотацию @Qualifier для явного указания имени бина. Имя совпадает с названием класса с маленькой первой буквой:
@Component
@Qualifier("creditCardPaymentService") // Или просто задай имя
public class CreditCardPaymentService implements PaymentService {
// ...
}
@Component
@Qualifier("payPalPaymentService")
public class PayPalPaymentService implements PaymentService {
// ...
}
// Явно указываем, какой бин нужен
@Service
public class OrderService {
@Autowired
@Qualifier("payPalPaymentService")
private PaymentService paymentService; // Выберет PayPalPaymentService
}
Решение 3: Имя переменной
Spring попытается найти бин по имени переменной. Имя переменной должно совпадать с названием класса (первая буква маленькая):
@Service
public class OrderService {
@Autowired
private PaymentService payPalPaymentService; // Выберет PayPalPaymentService по имени!
}
Решение 4: Инъекция всех реализаций
Инъектируй список всех реализаций и выбирай нужную в runtime:
@Service
public class OrderService {
@Autowired
private List<PaymentService> paymentServices;
public void processOrder(String paymentMethod) {
PaymentService service = paymentServices.stream()
.filter(s -> s.getClass().getSimpleName()
.contains(paymentMethod))
.findFirst()
.orElseThrow();
service.processPayment(100.0);
}
}
Решение 5: Кастомная аннотация
Создай собственную аннотацию-квалификатор для более красивого кода:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface CreditCard {
}
@Component
@CreditCard
public class CreditCardPaymentService implements PaymentService {
// ...
}
@Service
public class OrderService {
@Autowired
@CreditCard
private PaymentService paymentService; // Ясно и красиво
}
Приоритет разрешения
Spring применяет следующий приоритет при разрешении неоднозначности:
- @Qualifier (если указан явно) — самый высокий приоритет
- Имя переменной — совпадение с именем бина
- @Primary — первичный бин
- Исключение — если ничего не совпадает
Полный пример
// Интерфейс
public interface NotificationService {
void send(String message);
}
// Реализация 1
@Component
@Primary
public class EmailNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("Email: " + message);
}
}
// Реализация 2
@Component
public class SmsNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("SMS: " + message);
}
}
// Использование
@Service
public class AlertService {
@Autowired
private NotificationService notificationService; // Выберет EmailNotificationService (@Primary)
@Autowired
@Qualifier("smsNotificationService")
private NotificationService smsService; // Выберет SmsNotificationService (@Qualifier)
public void notifyUser(String message) {
notificationService.send(message); // Email
smsService.send(message); // SMS
}
}
Рекомендации
- Используй @Primary для выбора "по умолчанию" реализации
- Используй @Qualifier когда нужно явно указать конкретный бин
- Избегай совпадения имён переменных с именами бинов — это может привести к неожиданному поведению
- Документируй выбор в комментариях, если логика неочевидна
- Рассмотри использование профилей (@Profile) если выбор зависит от окружения
Итак, ответ: Spring выбросит исключение NoUniqueBeanDefinitionException. Для решения используй @Primary (одна реализация по умолчанию) или @Qualifier (явное указание нужной реализации).