Рубрики
Без рубрики

Спортивные мероприятия

Основы событий в Spring – создайте простое пользовательское событие, опубликуйте его и обработайте в прослушивателе.

Автор оригинала: 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(GenericSpringEvent event) {
        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, поэтому его должно быть легко импортировать и запускать как есть.