@Primary
– это эффективный способ использования автоматического обнаружения и связывания по типу с несколькими экземплярами, если возможно определить один основной компонент-кандидата. Если вам требуется больше контроля над процессом выборки, вы можете использовать аннотацию @Qualifier
в Spring. Вы можете связать значения квалификаторов с конкретными аргументами, сужая набор совпадений по типам таким образом, чтобы для каждого аргумента производилась выборка конкретного бина. В простейшем случае это может быть обычное описательное значение, как показано в следующем примере:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
Вы также можете задать аннотацию @Qualifier
на отдельные аргументы конструктора или параметры метода, как показано в следующем примере:
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;
}
// ...
}
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>
- Бин со значением квалификатора
main
связывается с аргументом конструктора, который имеет такое же уточненное значение. - Бин со значением квалификатора
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
в своем определении, как показано в следующем примере:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
Затем можно указать кастомный квалификатор для автоматически обнаруживаемых и связываемых полей и параметров, как показано в следующем примере:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
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 на основе аннотаций. В частности, см. раздел "Указание метаданных квалификатора с помощью аннотаций".
В некоторых случаях может быть достаточным использование аннотации без значения. Это может быть полезно, если аннотация служит более общей цели и может быть применена к нескольким различным типам зависимостей. Например, вы можете указать автономный каталог, поиск в котором можно осуществлять при отсутствии подключения к Интернету. Сначала определите простую аннотацию, как показано в следующем примере:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
Затем добавьте аннотацию к полю или свойству, которое будет автоматически связано, как показано в следующем примере:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
- Эта строка добавляет аннотацию
@Offline
.
class MovieRecommender {
@Autowired
@Offline
private lateinit var offlineCatalog: MovieCatalog
// ...
}
- Эта строка добавляет аннотацию
@Offline
.
Теперь в определении бина нужен только type
квалификатора, как показано в следующем примере:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- внедрите все зависимости, требуемые для этого бина -->
</bean>
- Этот элемент определяет квалификатор.
Также можно определить кастомные аннотации квалификаторов, которые принимают именованные атрибуты в дополнение или вместо простого атрибута value
. Если для поля или параметра, подлежащего автоматическому поиску и связыванию, задано несколько значений атрибутов, определение бина должно совпадать со всеми этими значениями атрибутов, чтобы считаться компонентом-кандидатом на автоматический поиск и связывание. В качестве примера рассмотрим следующее определение аннотации:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
В данном случае Format
– это перечисляемый тип, определенный следующим образом:
public enum Format {
VHS, DVD, BLURAY
}
enum class Format {
VHS, DVD, BLURAY
}
Поля, которые будут автоматически связаны, аннотируются кастомным классификатором и включают значения для обоих атрибутов: genre
и format
, как показано в следующем примере:
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;
// ...
}
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 в качестве неявной формы квалификации. Например, предположим, что у вас следующая конфигурация:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
@Configuration
class MyConfiguration {
@Bean
fun stringStore() = StringStore()
@Bean
fun integerStore() = IntegerStore()
}
Если предположить, что предыдущие бины реализуют типизированный интерфейс (то есть Store<String>
и Store<Integer>
), то можно аннотировать с помощью @Autowire
интерфейс Store
, а типизированный интерфейс используется в качестве квалификатора, как показано в следующем примере:
@Autowired
private Store<String> s1; // квалификатор <String>, внедряет бин stringStore
@Autowired
private Store<Integer> s2; // квалификатор <Integer>, внидряет бин integerStore
@Autowired
private lateinit var s1: Store<String> // квалификатор <String>, внедряет бин stringStore
@Autowired
private lateinit var s2: Store<Integer> // квалификатор <Integer>, внидряет бин integerStore
Типизированные квалификаторы также применяются при автоматическом обнаружении и связывании списков, экземпляров Map
и массивов. В следующем примере выполняется автоматический поиск и связывание типизированного List
:
// Внедряем все бины Store, если они имеют дженерик <Integer>
// Бины Store<String> не будут отображаться в этом списке
private List<Store<Integer>> s;
// Внедряем все бины 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
, то выбрано будет оно.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ