Что произойдет в Spring, если несколько бинов реализуют один интерфейс?

Ответ

При наличии нескольких бинов, реализующих один интерфейс, Spring по умолчанию не сможет однозначно разрешить зависимость при автоматическом связывании (@Autowired), что приведет к исключению NoUniqueBeanDefinitionException.

Проблема:

interface PaymentService { void pay(); }

@Service
class CreditCardService implements PaymentService { ... }

@Service
class PayPalService implements PaymentService { ... }

@Component
class OrderProcessor {
    @Autowired // ОШИБКА: NoUniqueBeanDefinitionException
    private PaymentService paymentService; // Какой бин выбрать? CreditCardService или PayPalService?
}

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

  1. Использование @Primary: Помечает один бин как предпочтительный по умолчанию.

    @Service
    @Primary // Этот бин будет выбран по умолчанию
    class CreditCardService implements PaymentService { ... }
  2. Использование @Qualifier: Прямое указание имени конкретного бина.

    @Component
    class OrderProcessor {
        @Autowired
        @Qualifier("payPalService") // Ссылается на бин с именем 'payPalService'
        private PaymentService paymentService;
    }
    
    @Service("payPalService") // Явное задание имени бина
    class PayPalService implements PaymentService { ... }
  3. Внедрение всех бинов (коллекции): Если логика требует использования всех реализаций.

    @Component
    class PaymentRouter {
        @Autowired // Внедряется List из всех бинов типа PaymentService
        private List<PaymentService> allPaymentServices;
    }
  4. Использование @Resource (JSR-250): Аналог @Autowired + @Qualifier, но работает по имени.

    @Component
    class OrderProcessor {
        @Resource(name = "creditCardService")
        private PaymentService paymentService;
    }

Выбор стратегии: @Primary подходит для выделения реализации по умолчанию. @Qualifier обеспечивает точный контроль и необходим, когда контекст определяет, какая реализация должна быть использована.

Ответ 18+ 🔞

О, смотри-ка, классика жанра! Сидит себе программист, пишет код, а тут бац — Spring ему в ебало кидает NoUniqueBeanDefinitionException. Ну, сука, как так-то? Я же всё правильно сделал, блядь!

Представь картину: у тебя есть интерфейс, типа PaymentService. И ты такой молодец, сделал две реализации: одна карточками платит, другая через PayPal. Аннотаций навешал, всё красиво. А потом пытаешься это чудо заинжектить.

@Component
class OrderProcessor {
    @Autowired // ОШИБКА: NoUniqueBeanDefinitionException
    private PaymentService paymentService; // Какой бин выбрать? CreditCardService или PayPalService?
}

И тут Spring, такой: «Ну, ёпта, чувак... А какую из них тебе, собственно, впендюрить-то? Обе ж подходят!». И швыряет тебе исключение, прямо в рожу. Волнение ебать, терпения ноль ебать.

Но не спеши головой об стенку биться, есть же способы эту неразбериху разрешить. Их, блядь, целая куча!

1. @Primary — сделай одного главным пацаном. Это как сказать Spring: «Слушай, вот этот бин — он у нас по умолчанию, если никто явно не попросит другого». Вешаешь аннотацию на ту реализацию, которая чаще всего нужна.

@Service
@Primary // Этот бин будет выбран по умолчанию
class CreditCardService implements PaymentService { ... }

Теперь, когда кто-то просит просто PaymentService, не уточняя, получит именно CreditCardService. Хуй с горы, просто и понятно.

2. @Qualifier — точный прицел по имени. А вот если тебе в разных местах нужны разные реализации? Тут уже @Primary не прокатит. Надо прямо пальцем ткнуть: «Давай-ка мне именно вот эту». Для этого явно даёшь бину имя и потом по нему ссылаешься.

@Component
class OrderProcessor {
    @Autowired
    @Qualifier("payPalService") // Ссылается на бин с именем 'payPalService'
    private PaymentService paymentService;
}

@Service("payPalService") // Явное задание имени бина
class PayPalService implements PaymentService { ... }

Всё, магия! Spring теперь точно знает, какую конкретно пиздопроебибну тебе подсунуть.

3. Забрать всех скопом (коллекция). А бывает, что тебе не один нужен, а все сразу. Например, чтобы пройтись по всем сервисам и что-то сделать. Тогда можно заинжектить список или мапку.

@Component
class PaymentRouter {
    @Autowired // Внедряется List из всех бинов типа PaymentService
    private List<PaymentService> allPaymentServices;
}

Spring сам соберёт всех, кто реализует PaymentService, и запихнёт в лист. Удобно, блядь, для всяких роутеров или стратегий.

4. @Resource — старый добрый JSR-250. Почти как @Autowired + @Qualifier, но работает немного по-другому — ищет бин в первую очередь по имени.

@Component
class OrderProcessor {
    @Resource(name = "creditCardService")
    private PaymentService paymentService;
}

Так какую же хуйню выбрать?

  • @Primary — когда у тебя есть явный фаворит, реализация по умолчанию. «В 90% случаев давайте вот эту».
  • @Qualifier — когда от контекста зависит, какая реализация нужна. «В этом сервисе — PayPal, а в том — кредитку». Точный контроль, никакой неоднозначности, блядь.

Вот и вся наука. Главное — не паниковать, когда Spring начинает истерить. Он просто хочет, чтобы ты был конкретнее. Как тот немой Герасим, который мог только «Муму» мычать, а тут ему два варианта подсовывают — он и охренел.