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.
Читайте также:
- Spring IoC контейнер: сканирование classpath, использование фильтров для настройки сканирования
- Spring IoC контейнер: сканирование classpath, @Component и дополнительные стереотипные аннотации
- Spring IoC контейнер: сканирование classpath, автоматическое определение классов и регистрация определений бинов
Комментарии
Отправить комментарий