Выбор бина для внедрения: @Qualifier, @Named, @Resource

beanconditionВ больших приложениях, основанных на Spring (или любом другом IoC фреймворке), может наступить такой день, когда при внедрении зависимостей образуется неоднозначность: одной зависимости удовлетворяет сразу несколько бинов, ведь выбор производится по совместимости типов внедряемого и запрашиваемого бинов. Что же тогда произойдёт?

Подготовка

Нам понадобится многомодульный пустой maven проект с Spring и JUnit:

В проекте создадим интерфейс и две его реализации:

Неоднозначные бины

Для начала я бы хотел посмотреть, что жё всё таки будет, если мы объявим конкурс «Два бина на одно место» 🙂 Поскольку Spring поддерживает несколько аннотаций для связывания зависимостей, я решил попробовать их все:

  • @Autowired — Spring-specific аннотация, которую можно применять и к полям и к конструкторам и даже к сеттерам.
  • @Resource  — Аннотация из JSR-250, позволяющая внедрять ресурсы в самом широком смысле: от бинов и до ссылок на jndi
  • @Inject — Самая последняя разработка, Dependency Injection for Java. При использовании со Spring можно считать её идентичной @Autowired

Код, который неоднозначен:

И тестовый класс к нему. Так как тесты отличаются только именем пакета и именем тестируемого класса, достаточно будет показать лишь один:

Результат исполнения немного предсказуем:

Неявный выбор бина по имени

Одно из самых простых решений, это намекнуть Spring’у, какой бин нам нужен, используя имена полей.

Каждый бин в Spring context имеет своё имя. Это име порождается либо из имени класса, либо явно задаётся в xml и grovvy конфигах, либо берётся из имени функции создания бина в java config.

Если мы назовём поле с неоднозначным типом бина по имени бина, Spring сможет самостоятельно сделать правильным выбор:

Неявное определение типа по имени работает со всеми аннотациями:

Явное указание бина по имени

Для тех, кто не любит ‘convention-over-configuration’, в Spring есть аннотация @Qualifier, позволяющая явно задать имя нужного бина:

Тесты остаются теми же самыми и точно так же проходят:

Однако я пропустил в этот раз класс AmbiguousInjectFine Аннотация Spring @Qualifier работает, разумеется, и с Inject, но есть и другой @Qualifier.

@Qualifier и задание бина по типу

JSR-330 тоже определяет аннотацию @Qualifier, но она несовместима с @qualifier из Spring и устроена несколько иначе.

Для использования этой аннотации вначале нужно определить собственную аннотацию, уникальную для каждого бина:

И применить эту аннотацию и к классу бина и к зависимости:

@Named как другой способ указания бина по имени

JSR-330 помимо нового @Qualifier принёс и аналог старого, который называется @Named и ведёт себя аналогичным образом:

Кроме задания имени бина для внедрения, @Named можно использовать и как замену Spring’овомоу @Component/@Service/@Repository/@Controller, при этом имя бина можно сразу задать как параметр @Named.

@Resource и всё ещё указание бина по имени

К моему облегчению это последний метод задания типа по имени и он специфичен только для аннотации @Resource. Имя бина передаётся параметром аннотации:

Нужно…больше…бинов…

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

Порядок внедрения бинов не определён. Внедрение коллекции бинов работает со всеми аннотациями.

Заключение и рекомендации

Использование стандартных аннотаций @Named/@Inject позволяет сделать код независимым от реализации IoC/CDI и в перспективе менять реализации как перчатки. Платой за это будет потеря Spring специфичного функционала, такого как @Required/@Lazy/etc.

С другой стороны, выбор между специализацией бина по имени или по типу упирается в вопрос удобства и безопасности. Специализация по типу, с @Qualified из JSR-330 однозначно связывает требуемый бин с его реализацией, но требует достаточно много подготовительного кода. Специализация по имени не требует код вообще, но может привести к неожиданным результатам, если имя будет не то или не у того бина.

Код примера доступен на github. Тесты модуля fail специально оставлены проваливающимися.