Spring IoC контейнер: конфигурация на основе аннотаций, настройка автопривязки на основе аннотаций с @Qualifier

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

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
    }

    // ...
}

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

<?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"/> 
        <!-- Бин со значением квалификатора main связан с аргументом конструктора, который квалифицируется с тем же значением. -->

        <!-- внедрить любые зависимости, требуемые этим бином -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/>
        <!-- Бин со значением квалификатора action связан с аргументом конструктора, который квалифицируется с тем же значением. --> 

        <!-- внедрить любые зависимости, требуемые этим бином -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

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

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

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

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

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

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

Попытка внедрить результаты из методов @Bean в один и тот же класс конфигурации также является сценарием самоссылки. Либо лениво разрешите такие ссылки в сигнатуре метода там, где это действительно необходимо (в отличие от автоматически связанного поля в классе конфигурации), либо объявите уязвимые методы @Bean как статические, отделив их от содержащего экземпляра класса конфигурации и его жизненного цикла. В противном случае такие 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/>, а затем указать тип и значение в соответствии с вашими пользовательскими аннотациями квалификатора. Тип сопоставляется с полным именем класса аннотации. С другой стороны, для удобства, если нет риска конфликта имен, вы можете использовать короткое имя класса. В следующем примере демонстрируются оба подхода:

<?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>

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

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;

    // ...
}

Kotlin

class MovieRecommender {

    @Autowired
    @Offline 
    private lateinit var offlineCatalog: MovieCatalog

    // ...
}

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

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> 
    <!-- внедрить любые зависимости, требуемые этим бином -->
</bean>

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

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 является перечислением (enum), определяемым следующим образом:

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

    // ...
}

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

<?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>


Читайте также:


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки