Как стать автором
Обновить

Spring-потрошитель: жизненный цикл Spring Framework

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров65K

Введение

Ни для кого не секрет, что 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) контекста.

SmartLifecycle - это расширение Lifecycle интерфейса. Отличие в том, что он срабатывает во время обновления (refresh) и закрытия (close) контекста.

Понимаю, что это может быть немного запутанным. Я постараюсь разложить все по полочкам далее.

Жизненный цикл контекста Spring-а

Жизненный цикл контекста состоит из 4-ёх этапов:

  1. Этап обновления (refresh) - автоматический

  2. Этап запуска (start) - вызывается методом ApplicationContext#start

  3. Этап остановки (stop) - вызывается методом ApplicationContext#stop

  4. Этап закрытия (close) - автоматический

Этап обновления контекста

  1. BeanFactory создает BeanFactoryPostProcessor-ы используя конструктор без аргументов

Стоит знать
  • BeanFactory может создать экземпляр BeanFactoryPostProcessor только с конструктором без аргументов. В противном случае вы получите сообщение об ошибке со следующим сообщением: No default constructor found.

  • Обратные вызовы инициализации и уничтожения не работают как у обычных bean-компоненты если вы используете конфигурацию на основе аннотаций. Но они работают если использовать конфигурации на основе XML. Подробности в Жизненный цикл bean-компонента.

  • Если вы пометили BeanFactoryPostProcessor как лениво инициализируемый, то BeanFactory проигнорирует это

  1. ApplicationContext вызывает метод BeanFactoryPostProcessor#postProcessBeanFactory

  2. BeanFactory creates BeanPostProcessor

Стоит знать
  • `ApplicationContext` позволяет внедрять зависимости в конструктор `BeanPostProcessor`, но такой компонент не будет обрабатываться `BeanPostProcessor` и вы получите следующее сообщение: `Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying`

  • Обратные вызовы инициализации и уничтожения работают как обычные bean-компоненты.

  1. ApplicationContext регистрирует BeanPostProcessor

  2. Инициализация singleton bean-компонентов. Подробности в Жизненный цикл bean-компонента.

  3. ApplicationContext проверяет флаг SmartLifecycle#isRunning и вызывает метод SmartLifecycle#start, если флаг имеет значение false

Стоит знать
  • Метод SmartLifecycle#start вызывается автоматически на этапе обновления (refresh), поскольку флаг SmartLifecycle#isAutoStartup по умолчанию имеет значение true

  • Метод Lifecycle#startне вызывается на этапе обновления. Он вызывается на этапе запуска (start). Начальная фаза запускается только с помощью ApplicationContext#start.

  1. ApplicationContext публикует ContextRefreshedEvent

  2. Методы обратного вызова, помеченные аннотацией @EventListener с типом параметра метода ContextRefreshedEvent, обрабатывают это событие. Также здесь может быть ApplicationListener

Стоит знать

Один метод может обрабатывать несколько событий. Например:

`@EventListener(classes = { ContextStartedEvent.class, ContextStoppedEvent.class })`

Этап запуска контекста

  1. ApplicationContext проверяет флаг Lifecycle#isRunning и вызывает метод Lifecycle#start, если флаг имеет значение false

  2. ApplicationContext проверяет флаг SmartLifecycle#isRunning и вызывает метод SmartLifecycle#start, если флаг имеет значение false. Да-да, контекст второй раз проходиться по объектам реализующие интерфейс SmartLifecycle

  3. ApplicationContext публикует ContextStartedEvent

  4. Методы обратного вызова, помеченные аннотацией @EventListener с типом параметра метода ContextStartedEvent, обрабатывают это событие. Также здесь может быть ApplicationListener

Этап остановки контекста

  1. ApplicationContext проверяет флаг SmartLifecycle#isRunning и вызывает метод SmartLifecycle#stop, если флаг имеет значение true

  2. ApplicationContext проверяет флаг Lifecycle#isRunning и вызывает метод Lifecycle#stop, если флаг имеет значение true

  3. ApplicationContext публикует ContextStoppedEvent

  4. Методы обратного вызова, помеченные аннотацией @EventListener с типом параметра метода ContextStoppedEvent, обрабатывают это событие. Также здесь может быть ApplicationListener

Этап закрытия контекста

  1. ApplicationContext публикует ContextClosedEvent

  2. Методы обратного вызова, помеченные аннотацией @EventListener с типом параметра метода ContextClosedEvent, обрабатывают это событие. Также здесь может быть ApplicationListener

  3. ApplicationContext проверяет флаг SmartLifecycle#isRunning и вызывает метод SmartLifecycle#stop, если флаг имеет значение true

Стоит знать

Это выполниться раньше если был запущен этап остановки контекста

  1. ApplicationContext проверяет флаг Lifecycle#isRunning и вызывает метод Lifecycle#stop, если флаг имеет значение true

Стоит знать

Это выполниться раньше если был запущен этап остановки контекста

  1. Уничтожение bean-компонентов. Подробности в Жизненный цикл bean-компонента

Жизненный цикл bean-компонента

Жизненный цикл bean-компонента состоит из 2-ух этапов:

  1. Этап инициализации

  2. Этап уничтожения

Этап инициализации bean-компонента

  1. BeanFactory создает bean-компонент

  2. Срабатывает статический блок инициализации

  3. Срабатывает не статический блок инициализации

  4. Внедрение зависимостей на основе конструктора

  5. Внедрение зависимостей на основе setter-ов

Стоит знать

Если вы используете комбинацию 2 подходов конфигурирования: на основе XML и на основе аннотаций, то следует знать, что внедрение зависимостей через аннотации выполняется перед внедрением зависимостей через XML. Таким образом, конфигурация XML переопределяет аннотации для свойств.

  1. Отрабатывают методы стандартного набора *Aware интерфейсов

  2. BeanPostProcessor#postProcessBeforeInitialization обрабатывает bean-компонент

  3. InitDestroyAnnotationBeanPostProcessor#postProcessBeforeInitialization вызывает методы обратного вызова, помеченные аннотацией @PostConstruct

  4. BeanFactory вызывает метод InitializingBean#afterPropertiesSet

  5. BeanFactory вызывает метод обратного вызова, зарегистрированный как initMethod

  6. BeanPostProcessor#postProcessAfterInitialization обрабатывает bean-компонент

Этап уничтожения bean-компонента

Этап уничтожения срабатывает только для singleton bean-компонентов, так как только эти компоненты храниться в BeanFactory.

  1. InitDestroyAnnotationBeanPostProcessor.postProcessBeforeDestruction вызывает методы обратного вызова, отмеченные как@PreDestroy

  2. BeanFactory вызывает метод InitializingBean#destroy

  3. 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. Да, я слизал эту фразу у Вовы Ломова ведущего канала Теплица социальных технологий.

Полезные ссылки

Теги:
Хабы:
Всего голосов 15: ↑11 и ↓4+11
Комментарии9

Публикации

Истории

Работа

Java разработчик
268 вакансий

Ближайшие события