← Назад к вопросам

Какой бин выберет Spring при @Autowired интерфейсе, если есть две его реализации?

1.8 Middle🔥 201 комментариев
#Spring Boot и Spring Data#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Разрешение неоднозначности при внедрении зависимостей

Проблема: несколько реализаций одного интерфейса

Когда в 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 применяет следующий приоритет при разрешении неоднозначности:

  1. @Qualifier (если указан явно) — самый высокий приоритет
  2. Имя переменной — совпадение с именем бина
  3. @Primary — первичный бин
  4. Исключение — если ничего не совпадает

Полный пример

// Интерфейс
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
    }
}

Рекомендации

  1. Используй @Primary для выбора "по умолчанию" реализации
  2. Используй @Qualifier когда нужно явно указать конкретный бин
  3. Избегай совпадения имён переменных с именами бинов — это может привести к неожиданному поведению
  4. Документируй выбор в комментариях, если логика неочевидна
  5. Рассмотри использование профилей (@Profile) если выбор зависит от окружения

Итак, ответ: Spring выбросит исключение NoUniqueBeanDefinitionException. Для решения используй @Primary (одна реализация по умолчанию) или @Qualifier (явное указание нужной реализации).