Spring IoC контейнер: сканирование classpath, определение метаданных компонента в компонентах

Компоненты Spring также могут предоставлять метаданные определения компонента в контейнер. Это можно сделать с помощью той же аннотации @Bean, которая используется для определения метаданных 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(). Однако он также вносит определение bean-компонента, у которого есть фабричный метод, ссылающийся на метод publicInstance(). Аннотация @Bean идентифицирует метод фабрики и другие свойства определения компонента, такие как значение квалификатора через аннотацию @Qualifier. Другие аннотации уровня метода, которые можно указать, - это @Scope, @Lazy и пользовательские аннотации квалификатора.

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

Поля и методы с автопривязкой поддерживаются, с дополнительной поддержкой автопривязки методов @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)
}

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

Начиная с Spring Framework 4.3, вы также можете объявить параметр метода фабрики типа InjectionPoint (или его более специфический подкласс: DependencyDescriptor) для доступа к запрашивающей точке внедрения, которая инициирует создание текущего компонента. Обратите внимание, что это относится только к фактическому созданию экземпляров компонентов, а не к внедрению существующих экземпляров. Как следствие, эта особенность больше всего подходит для bean-компонентов прототипа. Для других областей применения фабричный метод только когда-либо видит точку внедрения, которая инициировала создание нового экземпляра компонента в заданной области (например, зависимость, которая инициировала создание ленивого одноэлементного компонента). В таких сценариях вы можете использовать предоставленные метаданные точки внедрения с семантической тщательностью. В следующем примере показано, как использовать 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 обрабатываются иначе, чем их аналоги в классе Spring @Configuration. Разница в том, что классы @Component не улучшены в CGLIB для перехвата вызова методов и полей. Проксирование CGLIB - это средство, с помощью которого вызов методов или полей в методах @Bean в классах @Configuration создает ссылки на метаданные бина для взаимодействующих объектов. Такие методы не вызываются с обычной семантикой Java, а скорее проходят через контейнер, чтобы обеспечить обычное управление жизненным циклом и проксирование компонентов Spring, даже при обращении к другим компонентам посредством программных вызовов методов @Bean. Напротив, вызов метода или поля в методе @Bean внутри простого класса @Component имеет стандартную семантику Java без специальной обработки CGLIB или применения других ограничений.

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

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

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

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

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


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


Комментарии

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

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

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

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