Автор оригинала: Eugen Paraschiv.
1. Обзор
В этой статье мы обсудим как использовать события весной .
События – это одна из наиболее упущенных функций фреймворка, но и одна из наиболее полезных. И – как и многие другие вещи в Spring – публикация событий является одной из возможностей, предоставляемых ApplicationContext.
Есть несколько простых рекомендаций, которым нужно следовать:
- событие должно расширяться Application Event
- издатель должен ввести объект ApplicationEventPublisher
- слушатель должен реализовать интерфейс ApplicationListener
Дальнейшее чтение:
События контекста приложения Spring
Как сделать @Async весной
Руководство по языку выражения Spring
2. Пользовательское Событие
Spring позволяет нам создавать и публиковать пользовательские события, которые по умолчанию являются синхронными . Это имеет несколько преимуществ – например, слушатель может участвовать в контексте транзакции издателя.
2.1. Простое Событие Приложения
Давайте создадим простой класс событий – просто заполнитель для хранения данных событий. В этом случае класс event содержит строковое сообщение:
public class CustomSpringEvent extends ApplicationEvent { private String message; public CustomSpringEvent(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } }
2.2. Издатель
Теперь давайте создадим издателя этого события . Издатель создает объект события и публикует его всем, кто слушает.
Чтобы опубликовать событие, издатель может просто ввести ApplicationEventPublisher и использовать publish Event() API:
@Component public class CustomSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publishCustomEvent(final String message) { System.out.println("Publishing custom event. "); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message); applicationEventPublisher.publishEvent(customSpringEvent); } }
В качестве альтернативы класс publisher может реализовать интерфейс ApplicationEventPublisherAware – это также введет издатель событий при запуске приложения. Обычно проще просто ввести издателю файл @Autowire.
2.3. Слушатель
Наконец, давайте создадим слушателя.
Единственное требование к слушателю-быть бобом и реализовать ApplicationListener интерфейс:
@Component public class CustomSpringEventListener implements ApplicationListener{ @Override public void onApplicationEvent(CustomSpringEvent event) { System.out.println("Received spring custom event - " + event.getMessage()); } }
Обратите внимание, как наш пользовательский прослушиватель параметризуется общим типом пользовательского события, что делает типобезопасным метод onApplicationEvent () . Это также позволяет избежать необходимости проверять, является ли объект экземпляром определенного класса событий, и выполнять его приведение.
И, как уже обсуждалось – по умолчанию spring события синхронны – метод doStuffAndPublishAnEvent() блокируется до тех пор, пока все слушатели не закончат обработку события.
3. Создание Асинхронных Событий
В некоторых случаях синхронная публикация событий – это не совсем то, что мы ищем – нам может понадобиться асинхронная обработка наших событий .
Вы можете включить это в конфигурации, создав ApplicationEventMulticaster bean с исполнителем; для наших целей здесь SimpleAsyncTaskExecutor работает хорошо:
@Configuration public class AsynchronousSpringEventsConfig { @Bean(name = "applicationEventMulticaster") public ApplicationEventMulticaster simpleApplicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); return eventMulticaster; } }
Реализация события, издателя и прослушивателя остается такой же, как и раньше, но теперь прослушиватель будет асинхронно обрабатывать событие в отдельном потоке .
4. Существующие Рамочные Мероприятия
Сама весна публикует различные события из коробки. Например, ApplicationContext будет запускать различные события фреймворка, например ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent и т. Д.
Эти события предоставляют разработчикам приложений возможность подключаться к жизненному циклу приложения и контексту и при необходимости добавлять свою собственную логику.
Вот краткий пример слушателя, слушающего обновление контекста:
public class ContextRefreshedListener implements ApplicationListener{ @Override public void onApplicationEvent(ContextRefreshedEvent cse) { System.out.println("Handling context re-freshed event. "); } }
Чтобы узнать больше о существующих событиях фреймворка, посмотрите наш следующий учебник здесь .
5. Прослушиватель событий, управляемый аннотациями
Начиная с Spring 4.2, прослушиватель событий не обязательно должен быть бобом, реализующим интерфейс ApplicationListener – он может быть зарегистрирован в любом public методе управляемого боба через аннотацию @EventListener :
@Component public class AnnotationDrivenEventListener { @EventListener public void handleContextStart(ContextStartedEvent cse) { System.out.println("Handling context started event."); } }
Как и прежде, сигнатура метода объявляет тип события, который он использует.
По умолчанию прослушиватель вызывается синхронно. Однако мы можем легко сделать его асинхронным, добавив аннотацию @Async . Однако мы должны помнить, что включить асинхронную поддержку в приложении.
6. Поддержка дженериков
Также можно отправлять события с универсальной информацией в типе события.
6.1. Общее Событие Приложения
Давайте создадим общий тип события . В нашем примере класс event содержит любое содержимое и индикатор состояния success :
public class GenericSpringEvent{ private T what; protected boolean success; public GenericSpringEvent(T what, boolean success) { this.what = what; this.success = success; } // ... standard getters }
Обратите внимание на разницу между Generic Spring Event и Custom Spring Event . Теперь у нас есть возможность публиковать любое произвольное событие, и оно больше не должно расширяться из Application Event .
6.2. Слушатель
Теперь давайте создадим слушателя этого события . Мы могли бы определить слушателя, реализовав интерфейс ApplicationListener , как и раньше:
@Component public class GenericSpringEventListener implements ApplicationListener> { @Override public void onApplicationEvent(@NonNull GenericSpringEvent event) { System.out.println("Received spring generic event - " + event.getWhat()); } }
Но, к сожалению, это определение требует, чтобы мы унаследовали Generic Spring Event от класса Application Event . Итак, для этого урока давайте воспользуемся управляемым аннотациями прослушивателем событий, обсуждавшимся ранее .
Также можно сделать прослушиватель событий условным , определив логическое выражение SpEL в аннотации @EventListener . В этом случае обработчик событий будет вызван только для успешного Generic Spring Event of String :
@Component public class AnnotationDrivenEventListener { @EventListener(condition = "#event.success") public void handleSuccessful(GenericSpringEventevent) { System.out.println("Handling generic event (conditional)."); } }
Spring Expression Language (SpEL) – это мощный язык выражений, который подробно рассматривается в другом учебнике.
6.3. Издатель
Издатель событий аналогичен описанному выше . Но из-за стирания типа нам нужно опубликовать событие, которое разрешает параметр generics, по которому мы будем фильтровать. Например, класс Generic String Spring Event расширяет Generic Spring Event .
И есть альтернативный способ публикации событий . Если мы вернем ненулевое значение из метода, аннотированного @EventListener в качестве результата, SpringFramework отправит этот результат как новое событие для нас. Кроме того, мы можем публиковать несколько новых событий, возвращая их в коллекцию в результате обработки событий.
7. События, Связанные с Транзакцией
Этот абзац посвящен использованию аннотации @Transactioneventlistener . Чтобы узнать больше об управлении транзакциями, ознакомьтесь с учебником Транзакции с Spring и JPA .
Начиная с весны 4.2, фреймворк предоставляет новый @Transactioneventlistener аннотация, которая является расширением @EventListener , позволяющим привязать слушателя события к фазе транзакции. Привязка возможна к следующим этапам транзакции:
- AFTER_COMMIT (по умолчанию) используется для запуска события, если транзакция успешно завершена AFTER_ROLLBACK
- – если транзакция откатилась AFTER_COMPLETION
- – если транзакция завершена (псевдоним для AFTER_COMMIT и AFTER_ROLLBACK ) BEFORE_COMMIT
- используется для запуска события прямо перед транзакцией фиксацией
Вот краткий пример прослушивателя транзакционных событий:
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void handleCustom(CustomSpringEvent event) { System.out.println("Handling event inside a transaction BEFORE COMMIT."); }
Этот прослушиватель будет вызван только в том случае, если существует транзакция, в которой работает производитель событий и она вот-вот будет зафиксирована.
И если ни одна транзакция не выполняется, то событие вообще не отправляется, если только мы не переопределим его, установив атрибут fallback Execution в true .
8. Заключение
В этом кратком руководстве мы рассмотрели основы работы с событиями весной – создание простого пользовательского события, его публикация и последующая обработка в прослушивателе.
Мы также кратко рассмотрели, как включить асинхронную обработку событий в конфигурации.
Затем мы узнали об улучшениях, введенных в Spring 4.2, таких как управляемые аннотациями слушатели, улучшенная поддержка дженериков и привязка событий к фазам транзакций.
Как всегда, код, представленный в этой статье, доступен на Github . Это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.