В этой статье я хочу базово пройтись по Spring. Рассказать о возможностях конфигурации её бинов и немного залезть во внутрь.
IoC container - это контейнер, реализующий принцип Inversion of Control (IoC). Он управляет созданием, связыванием и жизненным циклом бинов, которые конфигурируются на различных этапах сборки приложения и затем добавляются в контекст.
org.springframework.beans
и org.springframework.context
пакеты являются основой для Spring Framework’s IoC container. BeanFactory - это интерфейс контейнера Spring, предоставляющий базовый функционал для создания и управления бинами. BeanFactory
используется в основном для простых приложений и в случаях, когда ресурсы ограничены. Это наиболее низкоуровневый интерфейс, предоставляющий базовые возможности по конфигурации и управлению бинами. ApplicationContext является под-интерфейсом BeanFactory. Он добавляет:
-
Упрощенную интеграцию со Spring AOP, что позволяет добавлять проксирование
-
Механизмы работы с интернационализацией, что позволяет разработчикам проще создавать приложения на различных языках
-
Механизмы публикации и обработки событий, что позволяет компонентам приложения общаться между собой (благодаря этому компоненты приложения станут более гибкими и менее связанными)
-
Свои специальные реализации:
-
WebApplicationContext
: Расширяет стандартныйApplicationContext
и добавляет функции, необходимые для работы с веб-приложениями, такие как поддержка сервлетов, фильтров, и интеграция с веб-сессиями -
AnnotationConfigApplicationContext
: Используется для конфигурации на основе аннотаций и Java-кода, что позволяет работать без XML-конфигурации -
GenericWebApplicationContext
: Позволяет использовать как традиционную конфигурацию на основе XML, так и конфигурацию на основе аннотаций и Java-кода -
ClassPathXmlApplicationContext
: Позволяет конфигурировать бины на основе вашего XML файла
-
Вкратце, BeanFactory
обеспечивает механизм конфигурации и базовое управление бинами в IoC контейнере, тогда как ApplicationContext
расширяет эти возможности, предлагая дополнительные функции, специфичные для корпоративных приложений.
На диаграмме ниже показано высокоуровневое представление о работе контейнера:
Как видно из диаграммы, Spring контейнер требует декларацию каждого бина. Эта декларация описывается при помощи аннотаций или XML. В ней описано метаданные для конфигурации бина (из какого класса нужно создавать, есть ли у него init() метод и как он называется, какие у него property, scope и далее). Метаданные бина Spring должен знать для того, чтобы его собрать.
Контейнер IoC Spring полностью независим от формата, в котором записаны метаданные конфигурации. Существует несколько способов настроить метаданные бинов:
-
Конфигурация на основе аннотаций: прямо в классе аннотациями размечать для Spring данные о будущем бине (@Component, @Service, @Repository, @Controller, @Autowired, @Qualifier)
@Service public class ExampleService { private int someValue; public void someMethod(){ System.out.println("I'm a service"); } public int getSomeValue() { return someValue; } public void setSomeValue(int someValue) { this.someValue = someValue; } }
@Component public class ExampleClass { private int value; @Autowired private ExampleService service; public void exampleMethod(){ System.out.println("Hello world!"); System.out.println("Service value: "+service.getSomeValue()); } public int getValue() { return value; } public void setValue(int value) { this.value = value; } }
-
Конфигурация на основе Java: для настройки метаданных о бине нужно создать отдельный конфигурационный класс (пометить его аннотацией @Configuration), а бины регистрировать при помощи метода (помеченного аннотацией @Bean), который возвращает объект нужного класса
@Configuration public class ConfigurationClass { @Bean public ExampleService service(){ return new ExampleService(); } @Bean public ExampleClass exampleClass(){ return new ExampleClass(service()); } }
-
XML конфигурация: все бины и их зависимости прописываются в XML файле
<!-- Определение бина ExampleService --> <bean id="service" class="com.example.ExampleService"/> <!-- Определение бина ExampleClass и внедрение зависимости service --> <bean id="exampleClass" class="com.example.ExampleClass"> <constructor-arg ref="service"/> </bean>
У каждого бина есть свой жизненный цикл (ЖЦ). Это время от момента создания и до уничтожения его из контекста.
BeanDefenition
– объект, который хранит информацию про бин
BeanPostProcessor (BPP)
– позволяет настраивать бины до того как они попали в IoC контейнер (он управляет аннотациями @Autowired, @Transactional, @Async и т.д.)
BeanFactoryPostProcessor (BFPP)
– класс, который позволяет настраивать BeanDefenition (т.е. еще до того как BeanFactory начала создавать бины). Может что-то изменять как в BeanDefenition, так и в самом BeanFactory перед тем, как он начнет работать и создавать бины из BeanDefenition.
Init() метод
– метод, который выполняется до попадания бина в IoC контейнер (после первого и до второго прохода по всем BPP). Можно прописать:
-
Если через xml, то через атрибут “init-method” в теге <bean>
-
Если работа с аннотациями, то поставить @PostConstract
Зачем использовать init-метод если есть конструктор? Потому что конструктор вызывается и используется BeanFactory для создания объекта и базовой инициализации данных. Однако, spring начнет дополнительно настраивать поля (например, какую-либо аннотацию для поля начнет обрабатывать BPP). Возникнет проблема если в конструкторе попытаться обратиться к данным, которые настраивает Spring уже после использования самого конструктора.
В Spring инициализация бина может проходить в три фазы:
-
Обычный конструктор класса
-
init-метод
-
Контекстные слушатели (ContextListener)
ApplicationListener
– слушает events контекста, и принимает соответствующие действия
Виды событий:
-
ContextStartedEvent (контекст начал свое построение, а когда заканчивает, то делает refresh)
-
ContextStoppedEvent (контекст приложения останавливается)
-
ContextRefreshedEvent (контекст был инициализирован или обновлен)
-
ContexClosedEvent (контекст приложения закрывается)
Зачем он нужен? Пример:
Во время старта приложения нужно “разогреть” кэш (для этого нужно сходить в БД, взять данные и обновить их у себя).
В конструкторе логику не написать, потому что на этапе использования конструктора бин еще вообще не готов, в init-метод не написать, потому что на этом этапе еще не
существует транзакции (@Transaction не будет настроена, т.к. init-метод сработает раньше, чем BPP настроит аннотацию). Остается дождаться полного создания контекста и прослушать событие refreshed.
Этапы создания бина:
-
Считываются все декларации бинов и кладет в специальную Map “
BeanDefenitions
” (id бина = декларация) -
BeanFactoryPostProcessor
изменяетBeanDefenitions
илиBeanFactory
(если настроено) -
После создания
BeanDefenitions
,BeanFactory
начинает по ним работать-
Берет бин
-
Настраивает его согласно конфигурации
-
Отсылает по очереди ко всем
BeanPostProcessor (BPP)
, потому что могут быть как кастомные, так и от spring (с аннотациями @Autowired, @Async, @Transactional и т.д.) -
У объекта вызывается init() метод
-
Еще один проход по всем
BPP
(на случай проксирования) -
Кладет в
IoC контейнер
(если scope бина singleton)
-
Так же, у каждого бина можно создать destroy-метод, который сработает перед его уничтожением. Можно прописать:
-
Если работа с аннотациями, то над методом поставить @PreDestroy
-
Если через xml, то через атрибут “destroy-method ” в теге <bean>
Заключение
Хочешь, хорошо работать – пользуйся Спрингом
Хочешь, чтобы работало хорошо – знай его кишкиЕвгений Борисов на вебинаре "Спринг Потрошитель"
Автор: VE_N_IK