Ответ
При наличии нескольких бинов, реализующих один интерфейс, 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?
}
Способы разрешения неоднозначности:
-
Использование
@Primary: Помечает один бин как предпочтительный по умолчанию.@Service @Primary // Этот бин будет выбран по умолчанию class CreditCardService implements PaymentService { ... } -
Использование
@Qualifier: Прямое указание имени конкретного бина.@Component class OrderProcessor { @Autowired @Qualifier("payPalService") // Ссылается на бин с именем 'payPalService' private PaymentService paymentService; } @Service("payPalService") // Явное задание имени бина class PayPalService implements PaymentService { ... } -
Внедрение всех бинов (коллекции): Если логика требует использования всех реализаций.
@Component class PaymentRouter { @Autowired // Внедряется List из всех бинов типа PaymentService private List<PaymentService> allPaymentServices; } -
Использование
@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 начинает истерить. Он просто хочет, чтобы ты был конкретнее. Как тот немой Герасим, который мог только «Муму» мычать, а тут ему два варианта подсовывают — он и охренел.