Введение
Никому не секрет, что Spring Framework один из самых популярных фреймворков для приложений на языке Java. Он интегрировал в себя самые полезные и актуальные технологии, такие как i18n, JPA, MVC, JMS, Cloud и т.п.
Но насколько хорошо вы знакомы с жизненным циклом фреймворка? Наверняка вы сталкивались с проблемами поднятия контекста и освобождением ресурсов при его остановке, когда проект разрастается. Сегодня я попытаюсь наглядно показать вам это.
Я решил изучить эту тему так как официальная документация достаточно разбросано отвечает на этот вопрос. В итоге я разработал небольшой тестовый стенд. Для тестирования я использовал специальные интерфейсы интеграции и аннотации которые позволяют расширить контейнер Spring-а, которые позволяют внедрить кастомную логику в его жизненный цикл. Я выбрал эти инструменты, так как там обычно и возникают проблемы. Я считаю, что это необходимо знать, чтобы избежать проблем на стадии разработки, отладки и тестирования.
К слову в этой статье не обсуждается почему жизненный цикл такой, какой он есть. Здесь я хочу вам показать результаты моих исследований и выводы. Также здесь не рассматриваются нюансы жизненного цикла иерархических контекстов.
Краткий экскурс
В данном подразделе я кратко расскажу об инструментах интеграции, которые предоставляет Spring. Вы можете пропустить его если вы знакомы со следующими понятиями: IoC Container, DI, BeanDefinition, BeanFactory, ApplicationContext, BeanFactoryPostProcessor, BeanPostProcessor, ApplicationListener, Lifecycle, SmartLifcycle.
Инверсия управления (Inversion of Control) — это принцип, при котором фреймворк вызывает пользовательский код. Это отличается от случая с библиотеками, потому что пользовательский код вызывает код библиотеки.
Внедрение зависимостей (Dependency Injection) — это шаблон проектирования, в котором объект получает объекты, от которых он зависит. Это отделяет создание объектов от их использования.
IoC Контейнер (IoC Container) — это реализация IoC и DI. Контейнер IoC создает и управляет bean-компонентами на основе мета-информации. Он также может решать и предоставлять зависимости для создаваемых им объектов.
BeanDefinition — описывает bean-компоненты. Создается на основе разобранной мета-информации.
BeanFactory — это интерфейс который создает и предоставляет bean-компоненты на основе BeanDefinition-ов. Он является ядром ApplicationContext.
ApplicationContext — это центральный интерфейс который предоставляете следующий список возможностей:
-
возможности BeanFactory
-
загрузка ресурсов
-
публикация событий
-
интернационализация
-
автоматическая регистрация
BeanPostProcessorиBeanFactoryPostProcessor
BeanFactoryPostProcessor — это интерфейс, который позволяет настраивать определения bean-компонентов контекста приложения. Он создается и запускается перед BeanPostProcessor.
BeanPostProcessor — это интерфейс для обеспечения интеграции кастомной логики создания экземпляров, разрешения зависимостей и т. д. Каждый компонент, созданный BeanFactory, проходит через каждый зарегистрированный BeanPostProcessor.
ApplicationContextEvent — основной класс для событий, возникающих в процессе жизненного цикла ApplicationContext. Его подклассы:
-
ContextRefreshedEvent — публикуется автоматически после поднятия контекста
-
ContextStartedEvent — публикуется методом
ApplicationContext#start -
ContextStoppedEvent — публикуется методом
ApplicationContext#stop -
ContextClosedEvent — публикуется автоматически перед закрытием контекста
ApplicationListener — интерфейс который позволяет обрабатывать ApplicationEvent события. Можно использовать аннотацию @EventListener вместо интерфейс.
Lifecycle — интерфейс похожий на ApplicationListener, но в нем определено 2 метода, которые срабатывают во время запуск (start) и остановку (stop) контекста.
SmartLifcycle — это расширение Lifecycle интерфейса. Отличие в том, что он срабатывает во время обновление (refresh) и закрытия (close) контекста.
Понимаю, что это может быть немного запутанным. Я постараюсь разложить все по полочкам далее.
Жизненный цикл контекста Spring-а
Жизненный цикл контекста состоит из 4-ёх этапов:
-
Этап обновления (refresh) — автоматический
-
Этап запуска (start) — вызывается методом
ApplicationContext#start -
Этап остановки (stop) — вызывается методом
ApplicationContext#stop -
Этап закрытия (close) — автоматический
Этап обновления контекста
-
BeanFactoryсоздаетBeanFactoryPostProcessor-ы используя конструктор без аргументов
Стоит знать
-
BeanFactoryможет создать экземплярBeanFactoryPostProcessorтолько с конструктором без аргументов. В противном случае вы получите сообщение об ошибке со следующим сообщением:No default constructor found. -
Обратные вызовы инициализации и уничтожения не работают как у обычных bean-компоненты. Хотя он помечен аннотацией
@Component. Подробности в Жизненный цикл bean-компонена. -
Если вы пометили
BeanFactoryPostProcessorкак лениво инициализируемый, тоBeanFactoryпроигнорирует это
-
ApplicationContextвызывает методBeanFactoryPostProcessor#postProcessBeanFactory -
BeanFactorycreatesBeanPostProcessor-ы
Стоит знать
-
`ApplicationContext` позволяет внедрять зависимости в конструктор `BeanPostProcessor`, но такой компонент не будет обрабатываться `BeanPostProcessor` и вы получите следующее сообщение: `Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying` -
Обратные вызовы инициализации и уничтожения работают как обычные bean-компоненты.
-
ApplicationContextрегистрируетBeanPostProcessor-ы -
Инициализация singleton bean-компонентов. Подробности в Жизненный цикл bean-компонента.
-
ApplicationContextпроверяет флагSmartLifecycle#isRunningи вызывает методSmartLifecycle#start, если флаг имеет значениеfalse
Стоит знать
-
Метод
SmartLifecycle#startвызывается автоматически на этапе обновления (refresh), поскольку флагSmartLifecycle#isAutoStartupпо умолчанию имеет значение true -
Метод
Lifecycle#startне вызывается на этапе обновления. Он вызывается на этапе запуска (start). Начальная фаза запускается только с помощьюApplicationContext#start.
-
ApplicationContextпубликуетContextRefreshedEvent -
Методы обратного вызова, помеченные аннотацией
@EventListenerс типом параметра методаContextRefreshedEvent, обрабатывают это событие. Также здесь может бытьApplicationListener
Стоит знать
Один метод может обрабатывать несколько событий. Например:
`@EventListener(classes = { ContextStartedEvent.class, ContextStoppedEvent.class })`
Этап запуска контекста
-
ApplicationContextпроверяет флагLifecycle#isRunningи вызывает методLifecycle#start, если флаг имеет значение false -
ApplicationContextпроверяет флагSmartLifecycle#isRunningи вызывает методSmartLifecycle#start, если флаг имеет значение false. Да-да, контекст второй раз проходиться по объектам реализующие интерфейсSmartLifecycle -
ApplicationContextпубликуетContextStartedEvent -
Методы обратного вызова, помеченные аннотацией
@EventListenerс типом параметра методаContextStartedEvent, обрабатывают это событие. Также здесь может бытьApplicationListener
Этап остановки контекста
-
ApplicationContextпроверяет флагSmartLifecycle#isRunningи вызывает методSmartLifecycle#stop, если флаг имеет значение true -
ApplicationContextпроверяет флагLifecycle#isRunningи вызывает методLifecycle#stop, если флаг имеет значение true -
ApplicationContextпубликуетContextStoppedEvent -
Методы обратного вызова, помеченные аннотацией
@EventListenerс типом параметра методаContextStoppedEvent, обрабатывают это событие. Также здесь может бытьApplicationListener
Этап закрытия контекста
-
ApplicationContextпубликуетContextClosedEvent -
Методы обратного вызова, помеченные аннотацией
@EventListenerс типом параметра методаContextClosedEvent, обрабатывают это событие. Также здесь может бытьApplicationListener -
ApplicationContextпроверяет флагSmartLifecycle#isRunningи вызывает методSmartLifecycle#stop, если флаг имеет значениеtrue
Стоит знать
Это выполниться раньше если был запущен этап остановки контекста
-
ApplicationContextпроверяет флагLifecycle#isRunningи вызывает методLifecycle#stop, если флаг имеет значениеtrue
Стоит знать
Это выполниться раньше если был запущен этап остановки контекста
-
Уничтожение bean-компонентов. Подробности в Жизненный цикл bean-компонента
Жизненный цикл bean-компонента
Жизненный цикл bean-компонента состоит из 2-ух этапов:
-
Этап инициализации
-
Этап уничтожения
Этап инициализации bean-компонента
-
BeanFactoryсоздает bean-компонент -
Срабатывает статический блок инициализации
-
Срабатывает не статический блок инициализации
-
Внедрение зависимостей на основе конструктора
-
Внедрение зависимостей на основе
setter-ов -
Отрабатывают методы стандартного набора
*Awareинтерфейсов -
BeanPostProcessor#postProcessBeforeInitializationобрабатывает bean-компонент -
InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitializationвызывает методы обратного вызова, помеченные аннотацией@PostConstruct -
BeanFactoryвызывает методInitializingBean#afterPropertiesSet -
BeanFactoryвызывает метод обратного вызова, зарегистрированный какinitMethod. -
BeanPostProcessor#postProcessAfterInitialization()обрабатывает bean-компонент
Этап уничтожения bean-компонента
Этап уничтожения срабатывает только для singleton bean-компонентов, так как только эти компоненты храниться в BeanFactory.
-
InitDestroyAnnotationBeanPostProcessor.postProcessBeforeDestructionвызывает методы обратного вызова, отмеченные как@PostConstruct -
BeanFactoryвызывает методInitializingBean#destroy -
BeanFactoryвызывает метод обратного вызова, зарегистрированный какdestroyMethod
Стоит знать
По умолчанию bean-компоненты, определенные с конфигурацией Java, которые имеют public метод close() или shutdown(), автоматически становятся методами обратного вызова уничтожения.
Дополнения к жизненному циклу
В данном подразделе я кратко расскажу об инструментах, о которых я не рассказывал ранее, так как они позволяют добавить более специфическую логику в жизненный цикл Spring-a. А моя цель была подробно разобрать типичный жизненный цикл.
Ordered — интерфейс, позволяющий управлять порядком работы компонентов. Например, если компоненты реализуют BeanPostProcessor/BeanFactoryPostProcessor и Ordered интерфейсы, то мы можем контролировать порядок их выполнения.
FactoryBean — интерфейс, позволяющий внедрить сложную логику создания объекта. Если у вас есть сложный код инициализации, который лучше выражается на Java, вы можете создать свой собственный FactoryBean, написать сложную инициализацию внутри этого класса, а затем подключить свой собственный FactoryBean к контейнеру.
ApplicationStartup — это инструмент, который помогает понять, на что тратится время на этапе запуска.
Еще существует механизм перезапуска приложения. Это может понадобиться в случаях:
-
Если нам нужно загрузить измененную мета-информацию
-
Если нужно изменить текущие активные профили
-
Если контекст упал и мы хотим иметь возможно автоматически поднять его
Подробнее об этом можно почитать в статье Programmatically Restarting a Spring Boot Application
Заключение
В этой статье мы изучили жизненный цикл Spring Framework, построили четкий алгоритм его работы. Мы изучили инструменты интеграции, которые позволяют использовать жизненный цикл в своих интересах, и выяснили роль каждого инструмента.
Эту статью вы можете использовать для подготовки к собеседованию, или как справочник, когда вы думаете как использовать инструменты Spring-а в своем проекте.
Все проверки я проводил на разработанном мною тестовом стенде, который наглядно показывает алгоритм жизненного цикла с использованием инструментов интеграции предоставляемых Spring Framework-ом.
Также у меня есть репозиторий, который тесно связан с темой этой статьи. Там вы найдете продублированный код из серии выступлений Spring-потрошитель от Евгения Борисова. Я создал этот репозиторий 4 года назад, так что facepalm гарантирую.
Спасибо за внимание и любите друг друга! Теперь это жизненно необходимо.
P.S. Да, я слизал эту фразу у Вовы Ломова ведущего канала Теплица социальных технологий.
Полезные ссылки
ссылка на оригинал статьи https://habr.com/ru/post/720794/
Добавить комментарий