Spring: IOC
Для запуска Spring необходим, по крайней мере, следующий набор библиотек:
- spring-core. Основные компоненты, классы, интерфейсы и т.д.
- spring-beans. Включает IOC и Depencency Injection. Дополняет spring-core
- spring-context. Реализует
ApplicationContext
. Дополняет 2 предыдущие библиотеки - spring-context-support. Позволяет подключать сторонние библиотеки
- spring-expressions. Реализует SpringExpressionLanguage (SpEL) для управления объектами
@Component
Маркирует класс как бин, включая его в Spring Context на основе конфигурации из @Configuration
после сканирования пакетов на наличие Spring бинов (см. @ComponentScan
)
Карта аннотаций
@Value | Добавляет переменные приложения в бин |
---|---|
@Autowired | Выполняет добавление зависимогго бина. Может применяться для полей класса, конструктора и setter-методов. Посредством @Qualifier детализирует инъекцию |
Аналогом является @Inject и @Resource из пакета javax.inject | |
@Required | Помечает зависимость от бина как обязательную. Начиная с Spring 5.1 может применяться только в конструкторе |
@Lazy | Помечает инъекцию как отложенную. Поиск кандидатов на удовлетворение зависимости выполянется только когда к переменной действительно идет обращение (в runtime) |
@DependsOn | Позволяет манипулировать очередностью инициализации и разрушения бинов. Зависимый позже создается и раньше разрушается |
@AliasFor | Предоставляет возможность выдачи псевдонимов членам аннотации |
@Secured | Определяет доступные роли для вызова Spring Web методов |
@Configuration
Вводит настройки работы Spring приложения. Может выполнять специфическую инициализацию бинов (см. @Bean
) или дополнительные аннотации (см. @Profile
, @Scope
)
Создание конфигурации из XML
Для инициализации из XML можно воспользоваться несколькими опциями для строки поиска:
- без префикса. Поиск в корневой директории для основного
ClassLoader
- classpath. Поиск в рамках известного JVM :classpath. Будет использован
ClassPathXmlApplicationContext
- file. поиск по абсолютному или относительному пути. Будет использован
FileSystemXmlApplicationContext
- http. Поиск используя указанный URL. Будет использован
WebApplicationContext
Создание @Bean
Выполняется в классе с аннотацией @Configuration
. Создаваемый бин должен создаваться в методе с целевым именем бина:
1 |
|
По умолчанию все бины в Spring являются Singleton
. Создание зависимых бинов так же допустимо
1 |
|
В принципе, любой объект можно сделать бином, используя @Bean
. При инъекции достаточно следовать рекомендациям по поиску кандидатов на инъекции.
@ComponentScan
Рекомендуется передавать пакет (в виде строки), в рамках которого будет осуществляться поиск бинов. В случае отсутствия данного ограничения Spring будет искать бины во всех библиотеках, входящих в известный JVM :classpath
static и @Bean
В случае если метод, создающий бин будет иметь идентификатор static
, он будет создаваться в момент инициализации ApplicationContext
, т.е. до самого бина @Configuration
Бин Environment
Предоставляет доступ к окружению, в котором работает Spring
Типы инъекций
В случае XML конфигурации, уникальность бинам придается с помощью атрибутов id
или name
. Для конфигурирования аннотациями используется имя класса (может быть передано при определении аннтотации).
Аннотации, которые автоматически создают бины: @Component
, @Repository
, @Service
, @Controller
, а так же, все расширяющие их.
При регистрации нового бина с помощью @Bean
можно передать несколько псевдонимов для него:
1 |
Аннотация @Description
не влияет ни не что, является исключительно информативной.
Инъекция через конструктор
Инъекции передаются через конструктор и перечисляются в качестве параметров:
1 | public class Resolver { |
Аннотация @Autowired
у конструктора является обязательной только в том случае если конструкторов несколько.
Поиск кандидатов на использование в качестве параметра-бина, выполняется последовательно:
- Поиск по типу
- Поиск по
Qualifier
- Поиск по имени параметра и имени бина
throw new NoUniqueBeanDefinitionException
в случае, если кандидатов несколькоthrow new NoSuchBeanDefinitionException
в случае, если кандидата найти не удалось
Выбор всегда отдается в пользу более приоритетного кандидата.
Стандартные @Autowired
и @Qualifier
можно заменить на @Inject
и @Named
(лучше делать именно так, это позволит избежать рефакторинга аннотаций при смене фреймворка, например, на Quarkus)
Инъекция @Value
Помимо бинов в @Autowired
можно вставлять примитивы из параметров приложения.
Ограничения @Autowired
В @Autowired
присутствует атрибут required
, который можно установить в значение false
. В этом случае наличие бина считается не обязательным (не будет выкинуто NoSuchBeanDefinitionException
). Но данная опция недопустима для конструктора!
Инъекция через setter
Все инъекции через setter выполняются после выполнения конструктора для создания объекта.
Метод, который следует использовать для инъекции необходимо помечать @Autowired
(или @Inject
). При этом, можно указать required
со значением false
(придется следить за возникновением NullPointerException).
Так же как и с конструктором - если инъекция считается обязательной, но кандидат на инъекцию не найден, то будет NoSuchBeanDefinitionException
.
Setter vs Constructor
Setter | Constructor |
---|---|
Может быть опциональным | Может быть final полем класса |
Позволяет избежать большого количества аргументов в конструкторе | Является обязательным |
Может порождать NPE |
Инъекции по полям
Аналогичен инъекциям через setter, но имеет ряд минусов: сложно накладывать тестирование; если required=false
, а кандидата не будет найдено, то будет NullPointerException
(!); ввиду того что приходится использовать Java Reflection, производительность немного снижается (критично при большом количестве Prototype
бинов)
Scope
Выделяются:
Singleton
. Одна сущность на все приложение. Тип по умолчанию. Определение: <ничего>,@Scope("singleton")
,@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
Prototype
. Каждое новое обращение выполняется к новой сущности. Определение:@Scope("prototype")
,@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Request
. Каждый новый HTTP запрос выполняется создание новой сущности. Определение:@Scope("request")
,@Scope(ConfigurableBeanFactory.SCOPE_REQUEST)
Session
. Каждое открытие новой HTTP сессии выполняется создание новой сущности. Определение:@Scope("session")
,@Scope(ConfigurableBeanFactory.SCOPE_SESSION)
Application
. Сущность создается одна для всего Global Application Context. Доступно только для web-aware Spring Application Context. Определение:@Scope("application")
,@Scope(ConfigurableBeanFactory.SCOPE_APPLICATION)
Socket
. Сущность создается для каждого нового обращения к socket. Уникально для WebSocket приложения. Определение:@Scope("socket")
,@Scope(ConfigurableBeanFactory.SCOPE_SOCKET)
Thread
. Должно быть подключено пользователем. Определение:@Scope("thread")
,@Scope(ConfigurableBeanFactory.SCOPE_THREAD)
Специфический scope
Де-факто просто псевдонимизация стандартных областей (scope). Ниже создание пользовательского scope:
1 |
|
Тогда применение пользовательского scope будет таким:
1 |
|
Или
1 |
|
Жизненый цикл бина
- Инициализация. Первый этап, при котором выполняется чтение описания бин, создание сущности, инъекция зависимых бинов, выделение ресурсов и т.д.
- Использование. Основной этап - использование в логике приложения. 99% срока жизни бина приходится именно на этот этап
- Разрушение. Высвобождение ресурсов, “разрыв” зависимостей, сборка мусора GC
При этом жизненый цикл бина с учетом контекста будет следующим:
- Создание
ApplicationContext
- Загрузка/чтение описания бинов
- Обработка описания бинов для их последующей инициализации
- Инициализация зависимых бинов
- Инъекция зависимостей
- Икончание инициализации бинов
- Использование согласно логики приложения
- Остановка
ApplicationContext
, разрушение бинов
При этом, пп. 2 и 3 включают:
- Поиск
@Configuration
- Поиск претендентов на регистрацию бинов относительно конфигураций
- вызов
BeanFactory
иPostProcessor
. Эти бины должны реализоватьorg.springframework.beans.factory.config.BeanFactoryPostProcessor
- они создаются автоматически до создания “обычных” бинов.
Ниже будут представлены способы перехвата инициализации бинов - Выполнение
@PostConstruct
методов после инъекции зависисмостей. Метод должен бытьvoid
и без параметров. Модификаторы доступа не важны (лучше выбрать удобные для тестирования), поскольку используется reflection. - Определение отложенной инициализации так же доступно при определении
@Bean(initMethod="...")
. Данная опция очень полезна, если невозможно изменить код бина
П. 8 допускает расширение стандартного разрушения способами:
- Путем определения
@PreDestroy
- Путем указания
@Bean(destroyMethod = "...")
Инъекция не-бинов
Аннотация @Value
умеет преобразовывать строки из параметров приложения в любые примитивы и их обертки.
Так же, по умолчанию, Spring умеет преобразовывать даты форматируя их из dd/MM/yyyy и yyyy/MM/dd
Для конвертации других типов следует использовать конвертер
1.Создать бин конвертера (можно через @Bean)
1 |
|
2.Добавить конвертер в фабрику
1 |
|
Использование SpEL
SpringExpressionLanguage может быть использован для вставки значений через @Value
1 |
|
Кастомизаци FactoryBean
1 | class MyFactoryBean implements FactoryBean<MyCustomBean> { |
@Autowired и дженерики
Инъекции типизированных бинов с помощью дженериков часто очень полезны:
1 |
|
При этом, если бы оба класса наследовались от одного типа JdbAbstractRepo<MarketEntity>
, Spring бы не смог определить какой бин требуется и бросил бы ошибку NoUniqueBeanDefinitionException
. Разрешить подобную проблему можно было бы с помощью @Qualifier("beanName")
.
Spring vs JSR
Спринг аннотации дублируют некоторые аннотации JSR:
Spring | JSR | Комментарий |
---|---|---|
@Component |
@Named |
Может заменить все кроме @Configuration |
@Qualifier |
@Qualifier |
|
@Autowired |
@Inject |
|
@Autowired + @Qualifier |
@Resource(name="beanName") |
@Lazy
Инъекция бина производится только во вромя первого к нему обращения. Удобно использовать если объект очень большой. Данную аннотацию следует использовать очень аккуроатно.
1 |
|
При инъекции признак обязательности необходимо снимать:
1 |
|
Но лучше использовать обертку в виде Optional в сочетаниии с setter:
1 | private Optional<SomeLazyBean> lazyBean = Optional.empty(); |