Для взаимодействия с механизмом управления жизненным циклом бинов контейнера можно реализовать интерфейсы 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"/>
Java
public class ExampleBean {
    public void init() {
        // выполняем некоторую инициализацию
    }
}
Kotlin
class ExampleBean {
    fun init() {
        // выполняем некоторую инициализацию
    }
}

Предыдущий пример позволяет получить почти точно такой же результат, как и следующий пример (состоящий из двух листингов):

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
Java
public class AnotherExampleBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() {
        // выполняем некоторую инициализацию
    }
}
Kotlin
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"/>
Java
public class ExampleBean {
    public void cleanup() {
        // выполняем некоторую работу по уничтожению (например, освобождаем пулы подключений)
    }
}
Kotlin
class ExampleBean {
    fun cleanup() {
        // выполняем некоторую работу по уничтожению (например, освобождаем пулы подключений)
    }
}

Предыдущее определение дает почти точно такой же результат, как и следующее:

<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
Java
public class AnotherExampleBean implements DisposableBean {
    @Override
    public void destroy() {
        // выполняем некоторую работу по уничтожению (например, освобождаем пулы подключений)
    }
}
Kotlin
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(). Тогда ваш класс будет похож на класс в следующем примере:

Java
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.");
        }
    }
}
Kotlin
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() для метода инициализации - для более чем одного из этих механизмов жизненного цикла, этот метод запускается один раз, как объяснялось в предыдущем разделе.

Несколько механизмов жизненного цикла, сконфигурированных для одного и того же бина, с разными методами инициализации, вызываются следующим образом:

  1. Методы, аннотированные с помощью @PostConstruct

  2. afterPropertiesSet(), как определено интерфейсом обратного вызова InitializingBean

  3. Специальный сконфигурированный метод init()

Методы уничтожения вызываются в том же порядке:

  1. Методы, аннотированные с помощью @PreDestroy

  2. destroy(), как определено интерфейсом обратного вызова DisposableBean

  3. Специальный сконфигурированный метод 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 в не-Web-приложениях

Этот раздел касается только не-веб-приложений. Реализации ApplicationContext для веб-приложений в фреймворке Spring содержат код для постепенного завершения работы IoC-контейнера Spring при завершении работы соответствующего веб-приложения.

Если вы используете IoC-контейнер Spring в среде приложений, отличных от веб-приложений (например, в полнофункциональной клиентской настольной среде), зарегистрируйте перехватчик завершения (shutdown hook) с помощью JVM. Таким способом можно обеспечить постепенное завершение работы и вызов соответствующих методов уничтожения для ваших бинов-одиночек с целью высвобождения всех ресурсов. Вам все равно требуется надлежаще сконфигурировать и реализовать эти обратные вызовы уничтожения.

Чтобы зарегистрировать перехватчик завершения, вызовите метод registerShutdownHook(), который объявлен в интерфейсе ConfigurableApplicationContext, как показано в следующем примере:

Java
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();
        // приложение запускается...
        // главный метод завершается, перехватчик вызывается перед завершением работы приложения...
    }
}
Kotlin
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
    val ctx = ClassPathXmlApplicationContext("beans.xml")
    // добавляем перехватчик завершения для вышеуказанного контекста...
    ctx.registerShutdownHook()
    // приложение запускается...
    // главный метод завершается, перехватчик вызывается перед завершением работы приложения...
}