Для взаємодії з механізмом управління життєвим циклом бінів
контейнера можна реалізувати інтерфейси InitializingBean
та DisposableBean
у Spring.
Контейнер викликає afterPropertiesSet()
для першого та destroy()
для другого, щоб бін міг
виконати певні дії під час ініціалізації та знищення твоїх бінів.
Анотації @PostConstruct
та @PreDestroy
,
стандартизовані JSR-250, зазвичай вважаються кращою методикою отримання зворотних викликів життєвого циклу в
сучасному додатку на Spring. Використання цих анотацій означає, що твоі біни не пов'язані з інтерфейсами,
специфічними для Spring.
Якщо не бажаєш використовувати анотації JSR-250, але все ж таки хочеш усунути зв'язаність, розглянь
метадані визначення біна init-method
та destroy-method
.
Всередині Spring
Framework використовує реалізації BeanPostProcessor
для обробки будь-яких інтерфейсів зворотного
виклику, які він може знайти, та виклику відповідних методів. Якщо тобі потрібні спеціальні функції або інша логіка
роботи життєвого циклу, які Spring не пропонує за замовчуванням, можеш реалізувати BeanPostProcessor
самостійно.
На додаток до зворотних викликів ініціалізації та знищення, об'єкти, керовані Spring, можуть
також
реалізовувати інтерфейс Lifecycle
, щоб ці об'єкти могли брати участь у процесі запуску та завершення,
що визначається власним життєвим циклом контейнера.
Інтерфейси зворотного виклику життєвого циклу описані в цьому розділі.
Зворотні виклики ініціалізації
Інтерфейс org.springframework.beans.factory.InitializingBean
дозволяє біну виконати ініціалізацію після того, як контейнер встановить усі необхідні властивості біну. Інтерфейс
InitializingBean
визначає єдиний метод:
void afterPropertiesSet() throws Exception;
Ми не рекомендуємо використовувати інтерфейс InitializingBean
, оскільки він занадто пов'язує код
зі
Spring. Як альтернативу можеш використовувати анотацію @PostConstruct
або вказати метод
ініціалізації POJO. У разі конфігураційних метаданих на основі XML можна використовувати атрибут
init-method
для зазначення імені методу, який повертає void і не має аргументів. У конфігурації Java
можна використовувати атрибут initMethod
у @Bean
. Розглянемо наступний приклад:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// виконуємо певну ініціалізацію
}
}
class ExampleBean {
fun init() {
// виконуємо певну ініціалізацію
}
}
Попередній приклад дозволяє отримати майже такий самий результат, як і наступний приклад (що складається з двох лістингів):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// виконуємо певну ініціалізацію
}
}
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// виконуємо певну ініціалізацію
}
}
Однак у першому з двох попередніх прикладів код не пов'язаний зі Spring.
Зворотні виклики знищення
Реалізація інтерфейсу org.springframework.beans.factory.DisposableBean
дозволяє біну отримати зворотний
виклик, коли контейнер, що містить його, знищується. Інтерфейс DisposableBean
визначає єдиний метод:
void destroy() throws Exception;
Ми не рекомендуємо використовувати інтерфейс зворотного виклику DisposableBean
, оскільки він
надмірно пов'язує код зі Spring. Як альтернативу пропонуємо використовувати анотацію @PreDestroy
або
вказати узагальнений метод, який підтримується визначеннями біна. При використанні метаданих конфігурації на основі
XML можна застосовувати атрибут destroy-method
на <bean/>
. У конфігурації Java
можна використовувати атрибут destroyMethod
у @Bean
. Розглянемо таке визначення:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// виконуємо певну роботу зі знищення (наприклад, звільняємо пули підключень)
}
}
class ExampleBean {
fun cleanup() {
// виконуємо певну роботу зі знищення (наприклад, звільняємо пули підключень)
}
}
Попереднє визначення дає майже такий самий результат, як і наступне:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// виконуємо певну роботу зі знищення (наприклад, звільняємо пули підключень)
}
}
class AnotherExampleBean : DisposableBean {
override fun destroy() {
// виконуємо певну роботу зі знищення (наприклад, звільняємо пули підключень)
}
}
Однак перше з двох попередніх визначень не пов'язує код зі Spring.
destroy-method
елемента <bean>
спеціальне значення (inferred)
, яке дає команду Spring автоматично виявляти публічний
close
або shutdown
метод для конкретного класу бінів. (Тому підійде будь-який клас, що
реалізує
java.lang.AutoCloseable
або java.io.Closeable
). Також можна встановити це
спеціальне (inferred)
значення для атрибуту default-destroy-method
елемента <beans>
,
щоб застосувати цю логіку роботи до всього набору бінів. Зверни увагу, що це логіка роботи за замовчуванням при
використанні конфігурації Java.
Методи ініціалізації та знищення за замовчуванням
Якщо ти пишеш
зворотні виклики методів ініціалізації та знищення, які не використовують специфічні для Spring інтерфейси зворотних
викликів InitializingBean
та DisposableBean
, зазвичай ти пишеш методи з такими
іменами, як init()
, initialize()
, dispose()
і таке інше. В ідеалі імена
таких методів зворотного виклику життєвого циклу стандартизовані в межах проєкту, щоб усі розробники
використовували одні й самі методи, що забезпечує узгодженість.
Можна налаштувати контейнер Spring на
"пошук" імен методів ініціалізації та зворотного виклику знищення кожного біна, якщо такі імена їм привласнено. Це
означає, що ти як розробник додатків можеш писати свої класи додатків та використовувати зворотний виклик
ініціалізації під назвою init()
без необхідності налаштовувати атрибут init-method="init"
для кожного визначення біна. IoC-контейнер Spring викликає цей метод при створенні біна (і відповідно до
стандартного контракту зворотного виклику життєвого циклу). Ця функція також забезпечує узгоджену угоду щодо
іменування зворотних викликів методів ініціалізації та знищення.
Припустимо, що твої методи зворотного
виклику ініціалізації мають ім'я init()
, а методи зворотного виклику знищення — destroy()
.
Тоді твій клас буде схожий на клас у наступному прикладі:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// це (що не дивно) метод зворотного виклику ініціалізації
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// це (що не дивно) метод зворотного виклику ініціалізації
fun init() {
if (blogDao == null) {
throw IllegalStateException("The [blogDao] property must be set.")
}
}
}
Тому ти можеш використовувати цей клас у бінах, схожих на те, що наведено нижче:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
Наявність атрибуту default-init-method
в атрибуті елемента верхнього рівня <beans/
>
призводить до того, що IoC-контейнер Spring розпізнає метод init
класу біна як зворотний
виклик методу ініціалізації. Коли бін створюється і компонується, якщо клас біна містить такий метод, він
ініціюється у відповідний момент.
Можна налаштувати зворотні виклики методу знищення аналогічним чином
(мається на увазі в XML) із використанням атрибуту default-destroy-method
в елементі верхнього рівня
<beans/>
.
У випадках, коли існуючі класи біна вже містять методи зворотного виклику, які
названі не відповідно до угоди, можна перевизначити значення за замовчуванням, зазначивши (мається на увазі XML)
ім'я методу за допомогою атрибутів init-method
та destroy-method
самого < bean/>
.
Контейнер Spring гарантує, що налаштований зворотний виклик ініціалізації буде викликаний відразу після того, як
бін отримає всі залежності. Таким чином, зворотний виклик ініціалізації викликається для посилання на необроблений
бін,
що означає, що перехоплювачі АОП тощо ще не застосовані до біна. Спочатку цілком створюється цільовий бін, а потім
застосовується АОП-проксі (наприклад) зі своїм ланцюжком перехоплювачів. Якщо цільовий бін та проксі визначені
окремо, твій код може навіть взаємодіяти з необробленим цільовим біном, минаючи проксі. Отже, було б непослідовно
застосовувати перехоплювачі до методу init
, оскільки в цьому випадку життєвий цикл цільового біну буде
пов'язаний з його проксі або перехоплювачами, що призведе до дивної семантики, коли твій код взаємодіятиме
безпосередньо з необробленим цільовим біном.
Об'єднання механізмів життєвого циклу
Починаючи з версії Spring 2.5, є три варіанти управління логікою роботи життєвого циклу біна:
Інтерфейси зворотного виклику
InitializingBean
таDisposableBean
Спеціальні методи
init()
таdestroy ()
Анотації
@PostConstruct
та@PreDestroy
. Можна об'єднувати ці механізми для керування конкретним біном.
init()
для методу
ініціалізації — для більш ніж одного з цих механізмів життєвого циклу, цей метод запускається один раз, як
пояснювалося в попередньому розділі.
Кілька механізмів життєвого циклу, налаштованих для одного й того ж біна, з різними методами ініціалізації, викликаються так:
Методи, анотовані за допомогою
@PostConstruct
afterPropertiesSet(),
як визначено інтерфейсом зворотного викликуInitializingBean
Спеціальний налаштований метод
init()
Методи знищення викликаються в тому ж порядку:
Методи, анотовані за допомогою
@PreDestroy
destroy(),
як визначено інтерфейсом зворотного викликуDisposableBean
Спеціальний налаштований метод
destroy()
Зворотні виклики під час запуску та завершення
Інтерфейс Lifecycle
визначає основні методи для будь-якого об'єкта, який має свої власні вимоги
до життєвого циклу (наприклад, запуск та зупинка будь-якого фонового процесу):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
Будь-який об'єкт, керований Spring, може реалізувати інтерфейс Lifecycle
. Потім, коли сам ApplicationContext
отримує сигнали запуску та зупинки (наприклад, для сценарію зупинки/перезапуску під час виконання), він
каскадує ці виклики для всіх реалізацій Lifecycle
, визначених у цьому контексті. Він робить це
шляхом делегування LifecycleProcessor
, як показано в наступному лістингу:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
Зверни увагу, що LifecycleProcessor
сам є розширенням інтерфейсу Lifecycle
.
Він також додає два інші методи для реагування на оновлення і закриття контексту.
Заметьте, что привычный интерфейс org.springframework.context.Lifecycle
є звичайним
контрактом для явних повідомлень про запуск та зупинку і не передбачає автоматичного запуску під час оновлення
контексту. Для більш тонкого контролю над автоматичним запуском конкретного біна (включно з фазою запуску),
розглянь можливість реалізації org.springframework.context.SmartLifecycle
.
Зверни увагу також на те, що повідомлення про зупинку не гарантовано приходять до знищення. У разі звичайного
завершення роботи всі біни Lifecycle
спершу отримують повідомлення про зупинку, перш ніж
поширяться загальні зворотні виклики знищення. Однак у разі гарячого оновлення (hot refresh) протягом
існування контексту або у разі зупинених спроб оновлення викликаються лише методи знищення.
Порядок викликів запуску та завершення роботи може відігравати важливу роль. Якщо між будь-якими двома
об'єктами існують відносини типу “depends-on”, то залежна сторона починає роботу після своєї залежності та
зупиняється перед своєю залежністю. Однак іноді прямі залежності невідомі. Ти можеш знати лише те, що
об'єкти певного типу повинні запускатись раніше об'єктів іншого типу. У цих випадках інтерфейс
SmartLifecycle
визначає інший варіант, а саме метод getPhase()
, визначений у його надінтерфейсі
Phased
. У наступному лістингу показано визначення Phased
інтерфейсу:
public interface Phased {
int getPhase();
}
У наступному лістингу показано визначення SmartLifecycle
інтерфейсу:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
Під час запуску об'єкти з найменшою фазою запускаються першими. У разі зупинки слід діяти у зворотному порядку.
Тому об'єкт, який реалізує SmartLifecycle
і чий метод getPhase()
повертає Integer.MIN_VALUE
,
буде одним з перших, хто розпочне роботу, і останнім, хто зупиниться. На іншому кінці спектра значення
фази Integer.MAX_VALUE
вказує на те, що об'єкт повинен розпочати роботу останнім і бути
зупинений першим (ймовірно, тому що він залежить від інших процесів, що запущені). При розгляді значення
фази також важливо знати, що фаза за замовчуванням для будь-якого "звичайного" об'єкта Lifecycle
,
який не реалізує SmartLifecycle
, дорівнює 0
. Тому будь-яке негативне значення фази
вказує на те, що об'єкт повинен розпочати роботу до цих стандартних компонентів (і зупинятися після них).
Зворотне твердження є правильним для будь-якого позитивного значення фази.
Метод зупинки, визначений SmartLifecycle
, приймає зворотний виклик. Будь-яка реалізація має
ініціювати метод run()
цього зворотного виклику після закінчення процесу завершення цієї
реалізації. Це забезпечує асинхронне завершення роботи, коли це необхідно, оскільки реалізація за
замовчуванням інтерфейсу LifecycleProcessor
, DefaultLifecycleProcessor
, чекає
протягом вказаного значення часу очікування, поки група об'єктів у кожній фазі не ініціює цей зворотний
виклик. За замовчуванням час очікування для кожної фази становить 30 секунд. Можна перевизначити
екземпляр процесора життєвого циклу за замовчуванням, визначивши бін з ім'ям lifecycleProcessor
у
контексті. Якщо потрібно змінити час очікування, достатньо визначити наступне:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- значення часу очікування в мілісекундах -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
Як згадувалося раніше, інтерфейс LifecycleProcessor
визначає методи зворотного виклику для
оновлення та закриття контексту. Останнє керує процесом завершення роботи, якби stop()
був
викликаний явно, але це відбувається при закритті контексту. З іншого боку, зворотний виклик оновлення
(refresh) дозволяє використовувати ще одну особливість бінів SmartLifecycle
. Якщо контекст
оновлюється (після того, як усі екземпляри об'єктів були створені та ініціалізовані), ініціюється цей
зворотний виклик. У цей момент процесор життєвого циклу за замовчуванням перевіряє булеве (логічне) значення,
що повертається методом isAutoStartup()
кожного об'єкта SmartLifecycle
. Якщо
true
, цей об'єкт запускається в цей момент, а не чекає явного виклику методу
start()
контексту або свого власного методу (на відміну від оновлення контексту, початок роботи
контексту не відбувається автоматично у стандартній реалізації контексту). Значення phase
та
будь-які відносини типу "depends-on" визначають порядок запуску, як описано раніше.
Поступове завершення роботи IoC-контейнера Spring у не-вебдодатках
Цей розділ стосується тільки не-додатків. Реалізації ApplicationContext
для вебдодатків у фреймворку Spring містять код для поступового завершення роботи IoC-контейнера Spring
при завершенні роботи відповідної вебпрограми.
Якщо ти використовуєш IoC-контейнер Spring у середовищі програм, відмінних від вебдодатків (наприклад, у повнофункціональному клієнтському десктопному середовищі), зареєструй перехоплювач завершення (shutdown hook) за допомогою JVM. У такий спосіб можна забезпечити поступове завершення роботи та виклик відповідних методів знищення для твоїх бінів-одинаків з метою вивільнення всіх ресурсів. Тобі все одно потрібно належно конфігурувати та реалізувати ці зворотні виклики знищення.
Щоб зареєструвати перехоплювач завершення, виклич метод registerShutdownHook()
, який оголошено
в інтерфейсі ConfigurableApplicationContext
, як показано в прикладі:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// додаємо перехоплювач завершення вищевказаного контексту...
ctx.registerShutdownHook();
// програма запускається...
// головний метод завершується, перехоплювач викликається перед завершенням роботи програми...
}
}
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
// додаємо перехоплювач завершення для зазначеного вище контексту...
ctx.registerShutdownHook()
// додаток запускається...
// головний метод завершується, перехоплювач викликається до завершення роботи програми...
}