1. Обзор
В этом уроке мы обсудим, как использовать события в Spring.
События — одна из самых недооцененных функций фреймворка, но также и одна из самых полезных. И, как и многое другое в Spring, публикация событий — это одна из возможностей, предоставляемых ApplicationContext
.
Есть несколько простых рекомендаций, которым нужно следовать:
- Класс события должен расширять
ApplicationEvent
, если мы используем версии до Spring Framework 4.2. Начиная с версии 4.2 , классы событий больше не должны расширять классApplicationEvent .
- Издатель должен внедрить объект
ApplicationEventPublisher
. - Слушатель должен реализовать интерфейс
ApplicationListener .
2. Пользовательское событие
Spring позволяет нам создавать и публиковать пользовательские события, которые по умолчанию являются синхронными. Это имеет несколько преимуществ, таких как возможность прослушивателя участвовать в контексте транзакции издателя.
2.1. Простое событие приложения
Давайте создадим простой класс события — просто заполнитель для хранения данных события.
В этом случае класс события содержит сообщение String:
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
и использовать API publishEvent()
:
@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);
}
}
В качестве альтернативы класс издателя может реализовать интерфейс ApplicationEventPublisherAware
, и это также будет внедрять издателя событий при запуске приложения. Обычно проще добавить издателю @Autowire
.
Начиная с Spring Framework 4.2, интерфейс ApplicationEventPublisher
предоставляет новую перегрузку для метода publishEvent (событие объекта)
, который принимает любой объект в качестве события. Поэтому событиям Spring больше не нужно расширять класс ApplicationEvent .
2.3. Слушатель
Наконец, давайте создадим прослушиватель.
Единственное требование к слушателю — быть bean-компонентом и реализовывать интерфейс ApplicationListener :
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
@Override
public void onApplicationEvent(CustomSpringEvent event) {
System.out.println("Received spring custom event - " + event.getMessage());
}
}
Обратите внимание, как наш пользовательский прослушиватель параметризован с помощью универсального типа пользовательского события, что делает метод onApplicationEvent()
типобезопасным. Это также избавляет от необходимости проверять, является ли объект экземпляром определенного класса событий, и приводить его.
И, как уже говорилось (по умолчанию события Spring являются синхронными ), метод doStuffAndPublishAnEvent()
блокируется до тех пор, пока все слушатели не закончат обработку события.
3. Создание асинхронных событий
В некоторых случаях синхронная публикация событий — это не совсем то, что нам нужно — нам может понадобиться асинхронная обработка наших событий.
Мы можем включить это в конфигурации, создав bean-компонент ApplicationEventMulticaster
с исполнителем.
Для наших целей здесь хорошо работает SimpleAsyncTaskExecutor
:
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster =
new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
Реализации события, издателя и слушателя остаются такими же, как и раньше, но теперь слушатель будет асинхронно обрабатывать событие в отдельном потоке.
4. Существующие фреймворковые события
Сам Spring из коробки публикует различные события. Например, ApplicationContext
будет запускать различные события фреймворка: ContextRefreshedEvent
, ContextStartedEvent
, RequestHandledEvent
и т. д.
Эти события предоставляют разработчикам приложений возможность подключиться к жизненному циклу приложения и контексту и добавить собственную пользовательскую логику, где это необходимо.
Вот краткий пример слушателя, прослушивающего обновления контекста:
public class ContextRefreshedListener
implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent cse) {
System.out.println("Handling context re-freshed event. ");
}
}
Чтобы узнать больше о существующих событиях фреймворка, ознакомьтесь с нашим следующим руководством здесь .
5. Слушатель событий, управляемый аннотациями
Начиная с Spring 4.2 прослушиватель событий не обязательно должен быть bean-компонентом, реализующим интерфейс ApplicationListener
— его можно зарегистрировать в любом общедоступном
методе управляемого bean-компонента с помощью аннотации @EventListener
:
@Component
public class AnnotationDrivenEventListener {
@EventListener
public void handleContextStart(ContextStartedEvent cse) {
System.out.println("Handling context started event.");
}
}
Как и прежде, сигнатура метода объявляет тип потребляемого им события.
По умолчанию прослушиватель вызывается синхронно. Однако мы можем легко сделать его асинхронным, добавив аннотацию @Async
. Нам просто нужно не забыть включить поддержку Async
в приложении.
6. Поддержка дженериков
Также можно отправлять события с общей информацией в типе события.
6.1. Общее событие приложения
Давайте создадим общий тип события.
В нашем примере класс события содержит любой контент и индикатор состояния успеха :
public class GenericSpringEvent<T> {
private T what;
protected boolean success;
public GenericSpringEvent(T what, boolean success) {
this.what = what;
this.success = success;
}
// ... standard getters
}
Обратите внимание на разницу между GenericSpringEvent
и CustomSpringEvent
. Теперь у нас есть возможность публиковать любое произвольное событие, и больше не требуется расширяться от ApplicationEvent
.
6.2. Слушатель
Теперь давайте создадим прослушиватель этого события.
Мы могли бы определить слушателя, реализовав интерфейс ApplicationListener
, как раньше:
@Component
public class GenericSpringEventListener
implements ApplicationListener<GenericSpringEvent<String>> {
@Override
public void onApplicationEvent(@NonNull GenericSpringEvent<String> event) {
System.out.println("Received spring generic event - " + event.getWhat());
}
}
Но это определение, к сожалению, требует, чтобы мы наследовали GenericSpringEvent
от класса ApplicationEvent .
Итак, для этого руководства давайте воспользуемся прослушивателем событий на основе аннотаций, который обсуждался ранее .
Также можно сделать прослушиватель событий условным , определив логическое выражение SpEL
в аннотации @EventListener.
В этом случае обработчик событий будет вызываться только для успешного GenericSpringEvent
of String
:
@Component
public class AnnotationDrivenEventListener {
@EventListener(condition = "#event.success")
public void handleSuccessful(GenericSpringEvent<String> event) {
System.out.println("Handling generic event (conditional).");
}
}
Spring Expression Language (SpEL) — это мощный язык выражений, который подробно рассматривается в другом учебнике.
6.3. Издатель
Издатель событий аналогичен описанному выше . Но из-за стирания типа нам нужно опубликовать событие, которое разрешает параметр generics, по которому мы будем фильтровать, например, class GenericStringSpringEvent extends GenericSpringEvent<String>
.
Кроме того, есть альтернативный способ публикации событий. Если мы вернем в качестве результата ненулевое значение из метода, аннотированного @EventListener
, Spring Framework отправит нам этот результат как новое событие. Более того, мы можем публиковать несколько новых событий, возвращая их в коллекцию в результате обработки событий.
7. События, связанные с транзакцией
Этот раздел посвящен использованию аннотации @TransactionalEventListener
. Чтобы узнать больше об управлении транзакциями, ознакомьтесь с Transactions With Spring и JPA .
Начиная с Spring 4.2, платформа предоставляет новую аннотацию @TransactionalEventListener
, которая является расширением @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.");
}
Этот прослушиватель будет вызываться только в том случае, если есть транзакция, в которой работает производитель событий, и она вот-вот будет зафиксирована.
И если ни одна транзакция не выполняется, событие вообще не отправляется, если только мы не переопределим это, установив для атрибута fallbackExecution значение
true
.
8. Заключение
В этой быстрой статье мы рассмотрели основы работы с событиями в Spring , включая создание простого пользовательского события, его публикацию и последующую обработку в прослушивателе.
Мы также кратко рассмотрели, как включить асинхронную обработку событий в конфигурации.
Затем мы узнали об улучшениях, представленных в Spring 4.2, таких как прослушиватели на основе аннотаций, улучшенная поддержка дженериков и привязка событий к фазам транзакций.
Как всегда, код, представленный в этой статье, доступен на GitHub . Это проект на основе Maven, поэтому его легко импортировать и запускать как есть.