В большинстве примеров в этой главе для указания конфигурационных метаданных, которые создают каждое BeanDefinition в контейнере Spring, используется XML. В предыдущем разделе показано, как предоставить большую часть конфигурационных метаданных через аннотации на уровне источника. Однако даже в этих примерах определения "базовых" бинов явно определены в XML-файле, а аннотации определяют только внедрение зависимостей. В этом разделе описывается возможность неявного обнаружения компонентов-кандидатов путем сканирования classpath. Компоненты-кандидаты – это классы, которые соответствуют критериям фильтра и имеют соответствующее определение бина, зарегистрированное в контейнере. Это устраняет необходимость использования XML для выполнения регистрации бина. Вместо этого вы можете использовать аннотации (например, @Component), выражения типов AspectJ или собственные кастомные критерии фильтрации, чтобы выбрать, какие классы имеют определения бина, зарегистрированные в контейнере.

Начиная со Spring 3.0, многие функции, предоставляемые проектом Spring JavaConfig, являются частью основной платформы Spring Framework. Это позволяет вам определять бины с помощью Java, а не с помощью традиционных XML-файлов. Взгляните на аннотации @Configuration, @Bean, @Import и @DependsOn как примеры использования этих новых возможностей.

@Component и дополнительные стереотипные аннотации

Аннотация @Repository – это маркер для любого класса, который исполняет роль или стереотип репозитория (также известного как объект доступа к данным или DAO). Среди способов использования этого маркера - автоматическое преобразование исключений.

Spring предоставляет дополнительные стереотипные аннотации: @Component, @Service и @Controller. @Component – это общий стереотип для любого компонента, управляемого Spring. @Repository, @Service и @Controller – это специализированные формы @Component для более конкретных случаев использования (на уровнях хранения, сервисном и представления, соответственно). Поэтому вы можете аннотировать свои компонентные классы с помощью @Component, но, если вместо этого аннотировать их @Repository, @Service или @Controller, ваши классы будут больше подходить для обработки инструментами или связи с аспектами. Например, эти стереотипные аннотации являются идеальными целями для срезов. @Repository, @Service и @Controller также могут нести дополнительную семантику в будущих выпусках Spring Framework. Таким образом, если вы делаете выбор между @Component и @Service для вашего сервисного уровня, @Service будет явно лучшим выбором. Аналогично, как было отмечено ранее, @Repository уже поддерживается как маркер для автоматического преобразования исключений на вашем уровне хранения.

Использование мета-аннотаций и составных аннотаций

Многие из аннотаций, предоставляемых Spring, могут быть использованы в качестве мета-аннотаций в вашем собственном коде. Мета-аннотация – это аннотация, которая может быть применена к другой аннотации. Например, аннотация @Service, упомянутая ранее, мета-аннотируется с помощью @Component, как показано в следующем примере:

Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component 
public @interface Service {
    // ...
}
  1. Аннотация @Component делает так, что аннотация @Service обрабатывается так же, как и @Component.
Kotlin
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Component 
annotation class Service {
    // ...
}
  1. Аннотация @Component делает так, что аннотация @Service обрабатывается так же, как и @Component.

Вы также можете комбинировать мета-аннотации для создания "составных аннотаций". Например, аннотация @RestController из Spring MVC состоит из @Controller и @ResponseBody.

Кроме того, составные аннотации могут по необходимости повторно объявлять атрибуты из мета-аннотаций для обеспечения возможности настройки. Это может быть особенно полезно, если нужно открыть только часть атрибутов мета-аннотации. Например, аннотация @SessionScope в Spring жестко кодирует имя области доступности как session, но при этом позволяет настраивать proxyMode. В следующем листинге показано определение аннотации SessionScope:

Java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
    /**
     * Псевдоним для {@link Scope#proxyMode}.
     * <p>По умолчение имеет значение {@link ScopedProxyMode#TARGET_CLASS}.
     */
    @AliasFor(annotation = Scope.class)
    ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
Kotlin
@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
@Scope(WebApplicationContext.SCOPE_SESSION)
annotation class SessionScope(
        @get:AliasFor(annotation = Scope::class)
        val proxyMode: ScopedProxyMode = ScopedProxyMode.TARGET_CLASS
)

Затем можно использовать @SessionScope без объявления proxyMode следующим образом:

Java
@Service
@SessionScope
public class SessionScopedService {
    // ...
}
Kotlin
@Service
@SessionScope
class SessionScopedService {
    // ...
}

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

Java
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
    // ...
}
Kotlin
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
class SessionScopedUserService : UserService {
    // ...
}

Более подробную информацию можно найти на вики-странице Spring Annotation Programming Model.

Автоматическое обнаружение классов и регистрация определений бинов

Spring может автоматически обнаруживать стереотипизированные классы и регистрировать соответствующие экземпляры BeanDefinition в ApplicationContext. Например, следующие два класса могут быть автоматически обнаружены:

Java
@Service
public class SimpleMovieLister {
    private MovieFinder movieFinder;
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
Kotlin
@Service
class SimpleMovieLister(private val movieFinder: MovieFinder)
Java
@Repository
public class JpaMovieFinder implements MovieFinder {
    // реализация опущена для ясности
}
Kotlin
@Repository
class JpaMovieFinder : MovieFinder {
    // реализация опущена для ясности
}

Для автоматического обнаружения этих классов и регистрации соответствующих бинов необходимо добавить @ComponentScan в класс @Configuration, где атрибут basePackages является общим родительским пакетом для двух классов. (Как вариант, можно указать список, разделенный запятыми, точкой с запятой или пробелами, который включает родительский пакет каждого класса).

Java
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"])
class AppConfig  {
    // ...
}
Для краткости в предыдущем примере можно было бы использовать атрибут value аннотации (то есть @ComponentScan("org.example")).

В следующей альтернативе используется XML:

<?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:component-scan base-package="org.example"/>
</beans>
Использование <context:component-scan> неявно активирует функциональность <context:annotation-config>. Обычно нет необходимости включать элемент <context:annotation-config> при использовании <context:component-scan>.

Сканирование пакетов classpath требует наличия соответствующих записей каталога в classpath. Если вы собираете JAR с помощью Ant, убедитесь, что не активируете переключатель "только файлы" в задаче JAR. Кроме того, каталоги пути классов могут быть закрыты на основании политик безопасности в некоторых средах – например, автономные приложения на JDK 1.7.0_45 и выше (что требует установки доверяемой библиотеки ('Trusted-Library') в ваших манифестах - см. https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources).

По пути модулей JDK 9 (Jigsaw) сканирование classpath Spring в целом работает так, как ожидается. Однако убедитесь, что классы ваших компонентов экспортированы в дескрипторы module-info. Если вы ожидаете, что Spring будет вызывать непубличные члены ваших классов, убедитесь, что они 'открыты' (то есть, что они используют объявление opens вместо объявления exports в вашем дескрипторе module-info).

Более того, AutowiredAnnotationBeanPostProcessor и CommonAnnotationBeanPostProcessor неявно включаются при использовании элемента component-scan. Это означает, что два компонента автоматически обнаруживаются и соединяются вместе - и все это без каких-либо конфигурационных метаданных бина, указанных на XML.

Вы можете отключить регистрацию AutowiredAnnotationBeanPostProcessor и CommonAnnotationBeanPostProcessor, включив атрибут annotation-config со значением false.

Использование фильтров для настройки сканирования

По умолчанию классы, аннотированные @Component, @Repository, @Service, @Controller, @Configuration или кастомной аннотацией, которая сама аннотирована @Component, являются единственными компонентами-кандидатами для обнаружения. Однако вы можете изменять и расширять эту логику работы, применяя кастомные фильтры. Добавьте их как атрибуты includeFilters или excludeFilters аннотации @ComponentScan (или как дочерние элементы <context:include-filter /> или <context:exclude-filter /> элемента <context:component-scan> в конфигурации XML). Каждый элемент фильтра требует наличия атрибутов type и expression. В следующей таблице описаны параметры фильтрации:

Table 5. Filter Types
Тип фильтра Пример выражения Описание

annotation (по умолчанию)

org.example.SomeAnnotation

Аннотация, которая должна быть present или meta-present на уровне типа в целевых компонентах.

assignable

org.example.SomeClass

Класс (или интерфейс), на который могут быть назначены (расширены или реализованы) целевые компоненты.

aspectj

org.example..*Service+

Выражение типа AspectJ, которому должны соответствовать целевые компоненты.

regex

org\.example\.Default.*

Регулярное выражение для сопоставления с именами классов целевых компонентов.

custom

org.example.MyTypeFilter

Кастомная реализация интерфейса org.springframework.core.type.TypeFilter.

В следующем примере показана конфигурация, игнорирующая все аннотации @Repository и использующая вместо них репозитории с "функцией-заглушкой":

Java
@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"],
        includeFilters = [Filter(type = FilterType.REGEX, pattern = [".*Stub.*Repository"])],
        excludeFilters = [Filter(Repository::class)])
class AppConfig {
    // ...
}

В следующем листинге показан эквивалент на XML:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
Вы также можете отключить фильтры по умолчанию, задав useDefaultFilters=false в аннотации или предоставив use-default-filters="false" в качестве атрибута элемента <component-scan/>. Таким способом можно эффективно отключить автоматическое обнаружение классов, аннотированных или мета-аннотированных @Component, @Repository, @Service, @Controller, @RestController или @Configuration.

Определение метаданных бинов в компонентах

Компоненты Spring могут также вносить метаданные определения бинов в контейнер. Это можно сделать с помощью той же аннотации @Bean, которая используется для определения метаданных бина в классах, помеченных аннотацией @Configuration. В следующем примере показано, как это сделать:

Java
@Component
public class FactoryMethodComponent {
    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
    public void doWork() {
        // Реализация метода компонента опущена
    }
}
Kotlin
@Component
class FactoryMethodComponent {
    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")
    fun doWork() {
        // Реализация метода компонента опущена
    }
}

Предыдущий класс является компонентом Spring, который имеет специфичный для приложения код в методе doWork(). Однако он также вносит определение бина, которое имеет фабричный метод, ссылающийся на метод publicInstance(). Аннотация @Bean идентифицирует фабричный метод и другие свойства определения бина, такие как значение квалификатора через аннотацию @Qualifier. Другие аннотации уровня метода, которые могут быть заданы, это @Scope, @Lazy и аннотации кастомных квалификаторов.

В дополнение к роли в инициализации компонентов вы также можете поместить аннотацию @Lazy на точки внедрения, помеченные @Autowired или @Inject. В данном контексте это приводит к внедрению прокси с отложенным разрешением. Однако такой подход с задействованием прокси довольно ограничен. Вместо него для сложных отложенных взаимодействий, в частности, в сочетании с необязательными зависимостями, мы рекомендуем ObjectProvider<MyTargetBean>.

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

Java
@Component
public class FactoryMethodComponent {
    private static int i;
    @Bean
    @Qualifier("public")
    public TestBean publicInstance() {
        return new TestBean("publicInstance");
    }
    // использование пользовательского квалификатора и автоматического обнаружения и связывания параметров метода
    @Bean
    protected TestBean protectedInstance(
            @Qualifier("public") TestBean spouse,
            @Value("#{privateInstance.age}") String country) {
        TestBean tb = new TestBean("protectedInstance", 1);
        tb.setSpouse(spouse);
        tb.setCountry(country);
        return tb;
    }
    @Bean
    private TestBean privateInstance() {
        return new TestBean("privateInstance", i++);
    }
    @Bean
    @RequestScope
    public TestBean requestScopedInstance() {
        return new TestBean("requestScopedInstance", 3);
    }
}
Kotlin
@Component
class FactoryMethodComponent {
    companion object {
        private var i: Int = 0
    }
    @Bean
    @Qualifier("public")
    fun publicInstance() = TestBean("publicInstance")
    // использование пользовательского квалификатора и автоматического обнаружения и связывания параметров метода
    @Bean
    protected fun protectedInstance(
            @Qualifier("public") spouse: TestBean,
            @Value("#{privateInstance.age}") country: String) = TestBean("protectedInstance", 1).apply {
        this.spouse = spouse
        this.country = country
    }
    @Bean
    private fun privateInstance() = TestBean("privateInstance", i++)
    @Bean
    @RequestScope
    fun requestScopedInstance() = TestBean("requestScopedInstance", 3)
}

В примере выполняется автоматическое обнаружение и связывание String параметра метода country со значением свойства age другого бина с именем privateInstance. Элемент языка выражений Spring Expression Language определяет значение свойства через обозначение #{ <expression> }. Для аннотаций @Value распознаватель выражений предварительно настроен на поиск имен бинов при разрешении текста выражения.

Начиная с версии Spring Framework 4.3, вы также можете объявить параметр фабричного метода типа InjectionPoint (или его более конкретного подкласса: DependencyDescriptor) для получения доступа к точке внедрения запроса, которая вызывает создание текущего бина. Обратите внимание, что это касается только фактического создания экземпляров бина, а не внедрения существующих экземпляров. Как следствие, эта функция имеет смысл для бинов, находящихся в области доступности prototype. Для других областей доступности фабричный метод распознает только точку внедрения, которая вызвала создание нового экземпляра бина в данной области доступности (например, зависимость, которая вызвала создание бина-одиночки с отложенной инициализацией). В таких сценариях можно использовать предоставленные метаданные точки внедрения с соблюдением семантической осторожности. В следующем примере показано, как использовать InjectionPoint:

Java
@Component
public class FactoryMethodComponent {
    @Bean @Scope("prototype")
    public TestBean prototypeInstance(InjectionPoint injectionPoint) {
        return new TestBean("prototypeInstance for " + injectionPoint.getMember());
    }
}
Kotlin
@Component
class FactoryMethodComponent {
    @Bean
    @Scope("prototype")
    fun prototypeInstance(injectionPoint: InjectionPoint) =
            TestBean("prototypeInstance for ${injectionPoint.member}")
}

Методы с аннотацией @Bean в обычном компоненте Spring обрабатываются иначе, чем их аналоги в классе с аннотацией@Configuration в Spring. Разница состоит в том, что классы @Component нельзя расширить с помощью CGLIB для перехвата вызовов методов и полей. CGLIB-проксирование – это средство, с помощью которого вызов методов или полей внутри методов, помеченных аннотацией @Bean, в классах с аннотацией @Configuration создает ссылки метаданных бина на взаимодействующие объекты. Такие методы не вызываются с использованием типовой семантики Java, а проходят через контейнер, чтобы обеспечить привычное управление жизненным циклом и проксирование бинов в Spring, даже при обращении к другим бинам через программные вызовы методов @Bean. В отличие от этого, вызов метода или поля в методе @Bean в обычном классе @Component имеет стандартную семантику Java, без специальной обработки CGLIB или других ограничений.

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

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

Видимость методов @Bean на языке Java не оказывает непосредственного влияния на результирующее определение бина в контейнере Spring. Вы можете свободно объявлять свои фабричные методы по своему усмотрению в классах, не относящихся к @Configuration, а также для статических методов в любом месте. Однако обычные методы @Bean в классах @Configuration должны быть переопределяемыми, то есть, они не должны быть объявлены как private или final.

Методы @Bean также обнаруживаются для базовых классов данного компонента или конфигурационного класса, а также для методов на Java 8 по умолчанию, объявленных в интерфейсах, реализуемых компонентом или конфигурационным классом. Это позволяет очень гибко подходить к созданию сложных конфигураций, при этом даже множественное наследование возможно осуществить через методы Java 8 по умолчанию, начиная с версии Spring 4.2.

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

Именование компонентов с автоматическим обнаружением

Если компонент автоматически обнаруживается в процессе сканирования, его имя бина генерируется стратегией BeanNameGenerator, известной данному сканеру. По умолчанию, любая стереотипная аннотация Spring(@Component, @Repository, @Service и @Controller), содержащая value имени, тем самым предоставляет это имя соответствующему определению бина.

Если такая аннотация не содержит value имени или для любого другого обнаруженного компонента (например, обнаруженного кастомными фильтрами), генератор имен бинов по умолчанию возвращает неполное имя класса без заглавных букв. Например, если были обнаружены следующие классы компонентов, то их имена будут myMovieLister и movieFinderImpl:

Java
@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
Kotlin
@Service("myMovieLister")
class SimpleMovieLister {
    // ...
}
Java
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Kotlin
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}

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

Если вы столкнулись с конфликтами имен из-за того, что несколько автоматически обнаруженных компонентов имеют одинаковые неполные имена классов (т.е. классы с одинаковыми именами, но находящиеся в разных пакетах), вам может понадобиться настроить BeanNameGenerator, который по умолчанию использует полное имя класса для генерируемого имени бина. Начиная со Spring Framework 5.2.3, для таких целей можно использовать FullyQualifiedAnnotationBeanNameGenerator, находящийся в пакете org.springframework.context.annotation.
Java
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], nameGenerator = MyNameGenerator::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

Как правило, следует указывать имя вместе с аннотацией во всех случаях, в которых другие компоненты могут явно ссылаться на него. С другой стороны, автоматически генерируемые имена подходят для тех случаев, когда контейнер отвечает за связывание.

Предоставление области доступности для автоматически обнаруженных компонентов

Как и в целом в случае с компонентами, управляемыми Spring, стандартной и наиболее распространенной областью доступности для автоматически обнаруженных компонентов является singleton. Однако иногда может потребоваться иная область доступности, которую можно указать с помощью аннотации @Scope. Вы можете указать имя области доступности в аннотации, как показано в следующем примере:

Java
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}
Kotlin
@Scope("prototype")
@Repository
class MovieFinderImpl : MovieFinder {
    // ...
}
Самоанализ аннотаций @Scope осуществляется только для конкретного класса бина (для аннотированных компонентов) или для фабричного метода (для методов @Bean). В отличие от определений бинов на XML, здесь нет понятия наследования определений бинов, а иерархии наследования на уровне классов не имеют значения для целей метаданных.

Для получения подробной информации о веб-специфических областях доступности, таких как "request" или "session" в контексте Spring, см. раздел "Области доступности Request, Session, Application и WebSocket". Как и в случае с готовыми аннотациями для этих областей доступности, вы также можете составлять свои собственные аннотации, используя мета-аннотации Spring: например, специальную аннотацию, мета-аннотированную @Scope("prototype") и, возможно, также объявляющую специальный режим с прокси, входящим в область доступности (scoped-proxy mode).

Чтобы указать кастомную стратегию для разрешения области доступности, а не полагаться на подход, основанный на аннотировании, вы можете реализовать интерфейс ScopeMetadataResolver. Обязательно включите конструктор по умолчанию без аргументов. Затем можно указать полностью уточненное имя класса при настройке сканера, как показано в следующем примере аннотации и определения бина:
Java
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], scopeResolver = MyScopeResolver::class)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>

При использовании некоторых не неодиночных областей видимости может потребоваться создание прокси для объектов в области видимости. Для этого для элемента component-scan имеется атрибут scoped-proxy. Три возможных значения: no, interfaces и targetClass. Например, следующая конфигурация приводит к получению стандартных динамических прокси из JDK:

Java
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
    // ...
}
Kotlin
@Configuration
@ComponentScan(basePackages = ["org.example"], scopedProxy = ScopedProxyMode.INTERFACES)
class AppConfig {
    // ...
}
<beans>
    <context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>

Укзание метаданных квалификатора с помощью аннотаций

Примеры в данном разделе демонстрируют использование аннотации @Qualifier и пользовательских аннотаций квалификаторов для обеспечения тонкого контроля при разрешении компонентов-кандидатов на автоматическое обнаружение и связывание. Поскольку эти примеры были основаны на определениях бинов на XML, метаданные квалификатора были указаны в определениях бинов-кандидатов с помощью qualifier или дочерних элементов meta элемента bean на XML. Если вы полагаетесь на сканирование classpath для автоматического обнаружения компонентов, вы можете предоставить метаданные квалификатора с аннотациями на уровне типов для класса-кандидата. Следующие три примера демонстрируют этот приём:

Java
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Qualifier("Action")
class ActionMovieCatalog : MovieCatalog
Java
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Genre("Action")
class ActionMovieCatalog : MovieCatalog {
    // ...
}
Java
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
    // ...
}
Kotlin
@Component
@Offline
class CachingMovieCatalog : MovieCatalog {
    // ...
}
Как и в случае с большинством альтернативных способов, основанных на аннотациях, следует помнить, что метаданные аннотации привязаны к определению самого класса, в то время как использование XML позволяет нескольким бинам одного типа указывать различные метаданные квалификатора, поскольку эти метаданные указываются для каждого экземпляра, а не для каждого класса.

Генерация индекса компонентов-кандидатов

Хотя сканирование путей класса происходит крайне быстро, можно улучшить производительность запуска больших приложений, создав статический список кандидатов во время компиляции. В этом режиме все модули, которые являются объектами сканирования компонентов, должны задействовать этот механизм.

Ваши существующие директивы @ComponentScan или <context:component-scan/> должны оставаться неизменными, чтобы запрашивать контекст на сканирование компонентов-кандидатов в определенных пакетах. Если ApplicationContext обнаруживает такой индекс, он автоматически использует его, а не сканирует classpath.

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

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-indexer</artifactId>
        <version>5.3.24</version>
        <optional>true</optional>
    </dependency>
</dependencies>

В Gradle 4.5 и более ранних версиях зависимость нужно объявлять в конфигурации compileOnly, как показано в следующем примере:

dependencies {
    compileOnly "org.springframework:spring-context-indexer:5.3.24"
}

В Gradle 4.6 и более поздних версиях зависимость нужно объявлять в конфигурации annotationProcessor, как показано в следующем примере:

dependencies {
    annotationProcessor "org.springframework:spring-context-indexer:5.3.24"
}

Артефакт spring-context-indexer генерирует файл META-INF/spring.components, который включается в jar-файл.

При работе с этим режимом в вашей IDE, spring-context-indexer должен быть зарегистрирован как обработчик аннотаций, чтобы убедиться, что индекс будет обновляться при обновлении компонентов-кандидатов.
Индекс активируется автоматически, когда файл META-INF/spring.components будет найден в пути классов. Если индекс частично доступен для некоторых библиотек (или сценариев использования), но не может быть создан для всего приложения, можно вернуться к обычному типу организации пути классов (как если бы индекс вообще отсутствовал), установив spring.index.ignore в true, либо как системное свойство JVM, либо через механизм SpringProperties.