@Primary – это эффективный способ использования автоматического обнаружения и связывания по типу с несколькими экземплярами, если возможно определить один основной компонент-кандидата. Если вам требуется больше контроля над процессом выборки, вы можете использовать аннотацию @Qualifier в Spring. Вы можете связать значения квалификаторов с конкретными аргументами, сужая набор совпадений по типам таким образом, чтобы для каждого аргумента производилась выборка конкретного бина. В простейшем случае это может быть обычное описательное значение, как показано в следующем примере:

Java
public class MovieRecommender {
    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;
    // ...
}
Kotlin
class MovieRecommender {
    @Autowired
    @Qualifier("main")
    private lateinit var movieCatalog: MovieCatalog
    // ...
}

Вы также можете задать аннотацию @Qualifier на отдельные аргументы конструктора или параметры метода, как показано в следующем примере:

Java
public class MovieRecommender {
    private MovieCatalog movieCatalog;
    private CustomerPreferenceDao customerPreferenceDao;
    @Autowired
    public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
    // ...
}
Kotlin
class MovieRecommender {
    private lateinit var movieCatalog: MovieCatalog
    private lateinit var customerPreferenceDao: CustomerPreferenceDao
    @Autowired
    fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
                customerPreferenceDao: CustomerPreferenceDao) {
        this.movieCatalog = movieCatalog
        this.customerPreferenceDao = customerPreferenceDao
    }
    // ...
}

В следующем примере показаны соответствующие определения бинов.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
  1. Бин со значением квалификатора main связывается с аргументом конструктора, который имеет такое же уточненное значение.
  2. Бин со значением квалификатора action связывается с аргументом конструктора, который имеет такое же уточненное значение.

В случае совпадения возврата имя бина считается значением квалификатора по умолчанию. Таким образом, вы можете определить бин с id значения main вместо вложенного элемента классификатора, что приведет к тому же совпадающему результату. Однако, хотя и можно использовать это соглашение для ссылки на конкретные бины по имени, в случае с аннотацией @Autowired речь идет в основном об управлемом типом внедрении зависимости с необязательными семантическими квалификаторами. Это означает, что значения квалификаторов, даже при возврате имени бина, всегда имеют сужающую семантику в пределах набора сопоставления типов. Они не выражают семантически ссылку на уникальный id бина. Надлежащими значениями квалификатора являются main или EMEA, или persistent, выражающие характеристики конкретного компонента, которые не зависят от id бина, который может быть автоматически сгенерирован в случае анонимного определения бина, как в предыдущем примере.

Квалификаторы также применяются к типизированным коллекциям, как обсуждалось ранее, например, к Set<MovieCatalog>. В этом случае все совпадающие бины, согласно объявленным квалификаторам, внедряются как коллекция. Это означает, что квалификаторы не обязательно должны быть уникальными. Скорее, они представляют собой критерии фильтрации. Например, можно определить несколько бинов MovieCatalog с одним и тем же значением классификатора "action", каждый из которых внедряется в Set<MovieCatalog>, аннотированный @Qualifier("action").

Предоставление возможности значениям квалификаторов делать выборку по именам целевых бинов в рамках сопоставленных по типу компонентов-кандидатов не требует аннотации @Qualifier в точке внедрения. Если нет иного индикатора разрешения (например, квалификатора или первичного маркера), в случае неуникальной зависимости Spring сопоставляет имя точки внедрения (то есть имя поля или параметра) с именами целевых бинов и выбирает одноименный компонент-кандидата, если таковой имеется.

Тем не менее, если вы намерены выразить управляемое аннотациями внедрение зависимости по имени, не используйте поначалу аннотацию @Autowired, даже если она позволяет делать выборку по имени бина среди сопоставленных по типу компонентов-кандидатов. Вместо этого используйте аннотацию @Resource из JSR-250, которая семантически определена для идентификации конкретного целевого компонента по его уникальному имени, при этом объявленный тип не имеет значения для процесса сопоставления. Аннотация @Autowired имеет совершенно другую семантику: После выборки бинов-кандидатов по типу, указанное значение квалификатора String учитывается только в рамках этих выбранных по типу компонентов-кандидатов (например, сопоставление квалификатора account с бинами, помеченными той же меткой квалификатора).

Для бинов, которые сами определены как тип коллекции, Map или массива, аннотация @Resource является отличным решением, которое ссылается на конкретный бин коллекции или массива по уникальному имени. Тем не менее, начиная с версии 4.3, можно сопоставлять типы коллекций, Map и массивов с помощью алгоритма сопоставления по типу @Autowired в Spring, если информация о типе элемента сохраняется в сигнатурах возвращаемых типов @Bean или в иерархии наследования коллекции. В этом случае можно использовать значения квалификатора для выборки среди однотипных коллекций, как описано в предыдущем пункте.

Начиная с версии 4.3, аннотация @Autowired также учитывает рекурсивные ссылки на внедрение зависимости (то есть ссылки обратно на бин, который в данный момент внедряется). Обратите внимание, что самовнедрение является возвратом. Обычные зависимости от других компонентов всегда имеют приоритет. В этом смысле рекурсивные ссылки не участвуют в выборке обычных компонентов-кандидатов и поэтому, в частности, никогда не являются первичными. Напротив, они всегда имеют наименьший приоритет. На практике рекурсивные ссылки следует использовать только в крайнем случае (например, для вызова других методов для того же экземпляра через транзакционный прокси бина). В таком сценарии следует учесть возможность вынесения затрагиваемых методов в отдельный бин-делегат. В качестве альтернативы можно использовать аннотацию @Resource, которая позволяет вернуть прокси к текущему бину по его уникальному имени.

Попытка внедрить результаты методов @Bean в один и тот же конфигурационный класс также фактически является сценарием с рекурсивной ссылкой. Либо отлаженно разрешайте такие ссылки в сигнатуре метода, когда это действительно необходимо (в отличие от автоматически связанного поля в конфигурационном классе), либо объявляйте затронутые методы аннотации @Bean как статические, отделяя их от содержащего их экземпляра конфигурационного класса и его жизненного цикла. В противном случае такие бины учитываются только на этапе возврата, а в качестве основных компонентов-кандидатов выбираются совпадающие бины других конфигурационных классов (если они доступны).

@Autowired применяется к полям, конструкторам и многоаргументным методам, позволяя сужать их с помощью аннотаций квалификатора на уровне параметров. И наоборот, @Resource поддерживается только для полей и сеттеров свойств бина с одним аргументом. Как следствие, следует использовать квалификаторы, если целью внедрения зависимости является конструктор или многоаргументный метод.

Вы можете создавать свои собственные аннотации квалификаторов. Для этого определите аннотацию и укажите аннотацию @Qualifier в своем определении, как показано в следующем примере:

Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
    String value();
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)

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

Java
public class MovieRecommender {
    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;
    private MovieCatalog comedyCatalog;
    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }
    // ...
}
Kotlin
class MovieRecommender {
    @Autowired
    @Genre("Action")
    private lateinit var actionCatalog: MovieCatalog
    private lateinit var comedyCatalog: MovieCatalog
    @Autowired
    fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
        this.comedyCatalog = comedyCatalog
    }
    // ...
}

Далее можно указать информацию для определений бинов-кандидатов. Вы можете добавить теги <qualifier/> в качестве подэлементов тега <bean/> и затем задать type и value в соответствии с вашими кастомными аннотациями квалификатора. Тип сопоставляется с полным именем класса аннотации. С другой стороны, если не возникает риска конфликта имен, можно использовать краткое имя класса для удобства. Следующий пример демонстрирует оба подхода:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>

В разделе "Сканирование переменной classpath и управляемые компоненты" вы можете увидеть альтернативу предоставлению метаданных классификатора в XML на основе аннотаций. В частности, см. раздел "Указание метаданных квалификатора с помощью аннотаций".

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

Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline

Затем добавьте аннотацию к полю или свойству, которое будет автоматически связано, как показано в следующем примере:

Java
public class MovieRecommender {
    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;
    // ...
}
  1. Эта строка добавляет аннотацию @Offline.
Kotlin
class MovieRecommender {
    @Autowired
    @Offline 
    private lateinit var offlineCatalog: MovieCatalog
    // ...
}
  1. Эта строка добавляет аннотацию @Offline.

Теперь в определении бина нужен только type квалификатора, как показано в следующем примере:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> 
    <!-- внедрите все зависимости, требуемые для этого бина -->
</bean>
  1. Этот элемент определяет квалификатор.

Также можно определить кастомные аннотации квалификаторов, которые принимают именованные атрибуты в дополнение или вместо простого атрибута value. Если для поля или параметра, подлежащего автоматическому поиску и связыванию, задано несколько значений атрибутов, определение бина должно совпадать со всеми этими значениями атрибутов, чтобы считаться компонентом-кандидатом на автоматический поиск и связывание. В качестве примера рассмотрим следующее определение аннотации:

Java
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
    String genre();
    Format format();
}
Kotlin
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)

В данном случае Format – это перечисляемый тип, определенный следующим образом:

Java
public enum Format {
    VHS, DVD, BLURAY
}
Kotlin
enum class Format {
    VHS, DVD, BLURAY
}

Поля, которые будут автоматически связаны, аннотируются кастомным классификатором и включают значения для обоих атрибутов: genre и format, как показано в следующем примере:

Java
public class MovieRecommender {
    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;
    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;
    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;
    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;
    // ...
}
Kotlin
class MovieRecommender {
    @Autowired
    @MovieQualifier(format = Format.VHS, genre = "Action")
    private lateinit var actionVhsCatalog: MovieCatalog
    @Autowired
    @MovieQualifier(format = Format.VHS, genre = "Comedy")
    private lateinit var comedyVhsCatalog: MovieCatalog
    @Autowired
    @MovieQualifier(format = Format.DVD, genre = "Action")
    private lateinit var actionDvdCatalog: MovieCatalog
    @Autowired
    @MovieQualifier(format = Format.BLURAY, genre = "Comedy")
    private lateinit var comedyBluRayCatalog: MovieCatalog
    // ...
}

Наконец, определения бинов должны содержать совпадающие значения квалификатора. Этот пример также демонстрирует, что вы можете использовать мета-атрибуты бина вместо элементов <qualifier/>. Если возможно, элемент <qualifier/> и его атрибуты имеют приоритет, но механизм автоматического обнаружения и связывания прибегает к значениям, предоставленным в тегах <meta/>, если такой квалификатор отсутствует, как в двух последних определениях бинов в следующем примере:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- внедрите все зависимости, требуемые для этого бина -->
    </bean>
</beans>

Использование дженериков в качестве квалификаторов автоматического обнаружения и связывания

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

Java
@Configuration
public class MyConfiguration {
    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }
    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }
}
Kotlin
@Configuration
class MyConfiguration {
    @Bean
    fun stringStore() = StringStore()
    @Bean
    fun integerStore() = IntegerStore()
}

Если предположить, что предыдущие бины реализуют типизированный интерфейс (то есть Store<String> и Store<Integer>), то можно аннотировать с помощью @Autowire интерфейс Store, а типизированный интерфейс используется в качестве квалификатора, как показано в следующем примере:

Java
@Autowired
private Store<String> s1; // квалификатор <String>, внедряет бин stringStore
@Autowired
private Store<Integer> s2; // квалификатор <Integer>, внидряет бин integerStore
Kotlin
@Autowired
private lateinit var s1: Store<String> // квалификатор <String>, внедряет бин stringStore
@Autowired
private lateinit var s2: Store<Integer> // квалификатор <Integer>, внидряет бин integerStore

Типизированные квалификаторы также применяются при автоматическом обнаружении и связывании списков, экземпляров Map и массивов. В следующем примере выполняется автоматический поиск и связывание типизированного List:

Java
// Внедряем все бины Store, если они имеют дженерик <Integer>
// Бины Store<String> не будут отображаться в этом списке
private List<Store<Integer>> s;
Kotlin
// Внедряем все бины Store, если они имеют обобщенный <Integer>
// Бины Store<String> не будут отображаться в этом списке
@Autowired
private lateinit var s: List<Store<Integer>>

1 Использование CustomAutowireConfigurer

CustomAutowireConfigurer – это BeanFactoryPostProcessor, который позволяет регистрировать собственные кастомные типы аннотаций квалификаторов, даже если они не аннотированы аннотацией @Qualifier фреймоврка Spring. Следующий пример показывает, как использовать CustomAutowireConfigurer:

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

AutowireCandidateResolver определяет компоненты-кандидатов на автоматическое связывание по:

  • Значению autowire-candidate для каждого определения бина

  • Любым шаблонам default-autowire-candidates, доступным для элемента <beans/>

  • Наличию аннотаций @Qualifier и любых кастомных аннотаций, зарегистрированных в CustomAutowireConfigurer

Если несколько бинов квалифицируются как компоненты-кандидаты на автоматический поиск и связывание, определение "первичного" происходит следующим образом: Если среди компонентов-кандидатов только одно определение бина имеет primary атрибут, установленный в true, то выбрано будет оно.