Spring IoC контейнер: дополнительные возможности ApplicationContext, стандартные и специальные события

Обработка событий в ApplicationContext обеспечивается через класс ApplicationEvent и интерфейс ApplicationListener. Если bean-компонент, реализующий интерфейс ApplicationListener, развертывается в контексте, каждый раз, когда ApplicationEvent публикуется в ApplicationContext, этот bean-компонент получает уведомление. По сути, это стандартный шаблон проектирования Observer.

Начиная с Spring 4.2, инфраструктура событий была значительно улучшена и предлагает модель на основе аннотаций, а также возможность публиковать любое произвольное событие (то есть объект, который не обязательно является наследником ApplicationEvent). Когда такой объект публикуется, Spring упаковывает его в событие для вас.

Ниже описаны стандартные события, которые предоставляет Spring:

ContextRefreshedEvent

Публикуется при инициализации или обновлении ApplicationContext (например, с помощью метода refresh() в интерфейсе ConfigurableApplicationContext). Здесь "инициализировано" означает, что все bean-компоненты загружены, bean-компоненты постпроцессора обнаружены и активированы, синглтоны созданы заранее и объект ApplicationContext готов к использованию. Пока контекст не был закрыт, обновление может запускаться несколько раз при условии, что выбранный ApplicationContext действительно поддерживает такие "горячие" обновления. Например, XmlWebApplicationContext поддерживает горячие обновления, а GenericApplicationContext - нет.

ContextStartedEvent

Публикуется при запуске ApplicationContext с помощью метода start() в интерфейсе ConfigurableApplicationContext. Здесь "запущен" означает, что все компоненты жизненного цикла получают явный сигнал запуска. Обычно этот сигнал используется для перезапуска bean-компонентов после явной остановки, но он также может использоваться для запуска компонентов, которые не были настроены для автозапуска (например, компонентов, которые еще не были запущены при инициализации).

ContextStoppedEvent

Публикуется, когда ApplicationContext останавливается с помощью метода stop() в интерфейсе ConfigurableApplicationContext. Здесь "остановлено" означает, что все компоненты жизненного цикла получают явный сигнал остановки. Остановленный контекст можно перезапустить с помощью вызова start().

ContextClosedEvent

Публикуется, когда ApplicationContext закрывается с помощью метода close() в интерфейсе ConfigurableApplicationContext или через обработчик завершения работы JVM. Здесь "закрыто" означает, что все singleton beans будут уничтожены. После закрытия контекста срок его службы истекает, и его нельзя обновить или перезапустить.

RequestHandledEvent

Веб-событие, сообщающее всем bean-компонентам, что HTTP-запрос был обработан. Это событие публикуется после выполнения запроса. Это событие применимо только к веб-приложениям, использующим DispatcherServlet Spring.

ServletRequestHandledEvent

Подкласс RequestHandledEvent, который добавляет контекстную информацию, специфичную для сервлета.

Вы также можете создавать и публиковать свои собственные события. В следующем примере показан простой класс, расширяющий базовый класс Spring ApplicationEvent:

Java

public class BlockedListEvent extends ApplicationEvent {

    private final String address;
    private final String content;

    public BlockedListEvent(Object source, String address, String content) {
        super(source);
        this.address = address;
        this.content = content;
    }

    // другие методы...
}

Kotlin

class BlockedListEvent(source: Any,
                    val address: String,
                    val content: String) : ApplicationEvent(source)

Чтобы опубликовать настраиваемое событие ApplicationEvent, вызовите метод publishEvent() для ApplicationEventPublisher. Обычно это делается путем создания класса, реализующего ApplicationEventPublisherAware, и регистрации его как bean-компонента Spring. В следующем примере показан такой класс:

Java

public class EmailService implements ApplicationEventPublisherAware {

    private List blockedList;
    private ApplicationEventPublisher publisher;

    public void setBlockedList(List blockedList) {
        this.blockedList = blockedList;
    }

    public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void sendEmail(String address, String content) {
        if (blockedList.contains(address)) {
            publisher.publishEvent(new BlockedListEvent(this, address, content));
            return;
        }
        // отправить email...
    }
}

Kotlin

class EmailService : ApplicationEventPublisherAware {

    private lateinit var blockedList: List
    private lateinit var publisher: ApplicationEventPublisher

    fun setBlockedList(blockedList: List) {
        this.blockedList = blockedList
    }

    override fun setApplicationEventPublisher(publisher: ApplicationEventPublisher) {
        this.publisher = publisher
    }

    fun sendEmail(address: String, content: String) {
        if (blockedList!!.contains(address)) {
            publisher!!.publishEvent(BlockedListEvent(this, address, content))
            return
        }
        // отправить email...
    }
}

Во время настройки контейнер Spring определяет, что EmailService реализует ApplicationEventPublisherAware, и автоматически вызывает setApplicationEventPublisher(). На самом деле переданный параметр - это сам контейнер Spring. Вы взаимодействуете с контекстом приложения через его интерфейс ApplicationEventPublisher.

Чтобы получить настраиваемый ApplicationEvent, вы можете создать класс, реализующий ApplicationListener, и зарегистрировать его как bean-компонент Spring. В следующем примере показан такой класс:

Java

public class BlockedListNotifier implements ApplicationListener {

    private String notificationAddress;

    public void setNotificationAddress(String notificationAddress) {
        this.notificationAddress = notificationAddress;
    }

    public void onApplicationEvent(BlockedListEvent event) {
        // уведомляем соответствующие стороны через notificationAddress...
    }
}

Kotlin

class BlockedListNotifier : ApplicationListener {

    lateinit var notificationAddres: String

    override fun onApplicationEvent(event: BlockedListEvent) {
        // уведомляем соответствующие стороны через notificationAddress....
    }
}

Обратите внимание, что ApplicationListener обычно параметризуется типом вашего настраиваемого события (BlockedListEvent в предыдущем примере). Это означает, что метод onApplicationEvent() может оставаться типобезопасным, избегая необходимости в понижающем преобразовании. Вы можете зарегистрировать столько слушателей событий, сколько захотите, но обратите внимание, что по умолчанию слушатели событий получают события синхронно. Это означает, что метод publishEvent() блокируется до тех пор, пока все слушатели не закончат обработку события. Одно из преимуществ этого синхронного и однопоточного подхода заключается в том, что, когда слушатель получает событие, он работает в контексте транзакции издателя, если контекст транзакции доступен.

В следующем примере показаны определения bean-компонентов, используемые для регистрации и настройки каждого из вышеуказанных классов:

<bean id="emailService" class="example.EmailService">
    <property name="blockedList">
        <list>
            <value>known.spammer@example.org</value>
            <value>known.hacker@example.org</value>
            <value>john.doe@example.org</value>
        </list>
    </property>
</bean>

<bean id="blockedListNotifier" class="example.BlockedListNotifier">
    <property name="notificationAddress" value="blockedlist@example.org"/>
</bean>

Собирая все вместе, когда вызывается метод sendEmail() bean-компонента emailService, если есть какие-либо сообщения электронной почты, которые следует заблокировать, публикуется настраиваемое событие типа BlockedListEvent. Bean-компонент blockedListNotifier регистрируется как ApplicationListener и получает событие BlockedListEvent, после чего он может уведомить соответствующие стороны.

Механизм событий Spring разработан для простой связи между компонентами Spring в одном контексте приложения. Однако для более сложных потребностей интеграции предприятия отдельно поддерживаемый проект Spring Integration обеспечивает полную поддержку для создания облегченных, шаблонно-ориентированных, управляемых событиями архитектур, основанных на хорошо известной модели программирования Spring.


Читайте также:


Комментарии

Популярные сообщения из этого блога

Как получить текущий timestamp в Java

Методы класса Object в Java

Основные опции JVM для повышения производительности и отладки