Как Spring разрешает автозавязывание (`@Autowired`) интерфейса, для которого существует несколько реализаций-бинов?

Ответ

Поведение зависит от конфигурации:

  1. Если одна реализация помечена @Primary — будет выбран этот бин.
  2. Если нет @Primary, но имя поля совпадает с именем одного из бинов — может быть выбран этот бин (но это поведение не всегда однозначно).
  3. В остальных случаях — Spring выбросит NoUniqueBeanDefinitionException.

Пример проблемы и решений:

public interface PaymentService {
    void process();
}

@Service("cardService")
public class CardPaymentService implements PaymentService { /*...*/ }

@Service("paypalService")
public class PayPalPaymentService implements PaymentService { /*...*/ }

@Component
public class OrderProcessor {
    @Autowired // ОШИБКА: NoUniqueBeanDefinitionException
    private PaymentService paymentService;
}

Способы разрешения:

  • Использование @Primary:
    @Service
    @Primary // Теперь этот бин будет выбран по умолчанию
    public class CardPaymentService implements PaymentService { /*...*/ }
  • Использование @Qualifier (предпочтительно для ясности):
    @Component
    public class OrderProcessor {
        @Autowired
        @Qualifier("paypalService") // Явно указываем, какую реализацию использовать
        private PaymentService paymentService;
    }
  • Инжекция Map или List всех реализаций:
    @Autowired
    private Map<String, PaymentService> allPaymentServices; // Ключ — имя бина

Ответ 18+ 🔞

Давай разберём эту весёлую ситуацию, когда Спринг смотрит на твой код и начинает чесать репу. Представь: у тебя есть интерфейс, типа PaymentService. А реализаций — две. Или три. Или овердохуища. И ты такой пишешь в классе @Autowired private PaymentService paymentService;.

И тут Спринг, этот хитрожопый фреймворк, останавливается, чешет затылок и говорит: «Бля, чувак, а какую из них тебе сунуть-то? Вот эта платит картой, а эта — через какую-то там палку. Они же разные, ёпта!». И швыряет тебе в лицо исключение NoUniqueBeanDefinitionException. По сути, он орёт: «Я нихуя не понял, выбери сам, мудак!».

Так, а как он вообще решает, что куда пихать? Есть правила, как в подъезде:

  1. Если одна из твоих реализаций помечена @Primary — всё, вопрос закрыт. Спринг такой: «А, ну тут главная помечена, значит её и впендюрим». Без вариантов.
  2. Если @Primary нет, но имя поля совпадает с именем одного из бинов — тут он может догадаться. Может. Но это как игра в русскую рулетку — поведение не всегда однозначное, лучше на это не рассчитывать.
  3. Во всех остальных случаях — получай свою NoUniqueBeanDefinitionException прямо в ебало. Пиздец и точка.

Вот смотри, живой пример, как всё это выглядит в коде:

public interface PaymentService {
    void process();
}

@Service("cardService")
public class CardPaymentService implements PaymentService { /*...*/ }

@Service("paypalService")
public class PayPalPaymentService implements PaymentService { /*...*/ }

@Component
public class OrderProcessor {
    @Autowired // ОШИБКА: NoUniqueBeanDefinitionException. Спринг в ахуе.
    private PaymentService paymentService;
}

Ну и что делать, спросишь ты? Вариантов — как говна за баней.

  • Воткнуть @Primary (способ для ленивых): Просто помечаешь одну реализацию как главную. Спринг успокаивается и всегда тыкает её.

    @Service
    @Primary // Теперь этот бин — царь и бог, его и будут совать везде по умолчанию
    public class CardPaymentService implements PaymentService { /*...*/ }
  • Использовать @Qualifier (способ для адекватных): Это предпочтительнее, потому что сразу видно, какую именно хуйню ты хочешь. Явно указываешь имя бина.

    @Component
    public class OrderProcessor {
        @Autowired
        @Qualifier("paypalService") // Чётко говорим: «Давай сюда ту, что через палку, а не карточную!»
        private PaymentService paymentService;
    }
  • Заинжектить Map или List всех реализаций (способ для извращенцев): А хочешь сразу все? Получи! Spring аккуратно сложит все бины этого типа в коллекцию. В Map ключом будет имя бина.

    @Autowired
    private Map<String, PaymentService> allPaymentServices; // Соберёт всё: и "cardService", и "paypalService"
    // Потом сам решай, какую из них взять и куда применить.

Короче, суть в том, чтобы не доводить Spring до состояния «терпения ноль ебать». Либо дай ему однозначную команду через @Qualifier, либо назначь главную через @Primary, либо забери всё разом и разбирайся сам. Выбор за тобой, чувак.