Весенние события

Весенние события

1. обзор

В этой статье мы обсудимhow to use events in Spring.

События являются одной из наиболее игнорируемых функциональных возможностей в структуре, но также и одной из наиболее полезных. И, как и многое другое в Spring, публикация событий - одна из возможностей, предоставляемыхApplicationContext.

Есть несколько простых рекомендаций:

  • событие должно продолжатьсяApplicationEvent

  • издатель должен внедрить объектApplicationEventPublisher

  • слушатель должен реализовать интерфейсApplicationListener

2. Пользовательское событие

Spring позволяет создавать и публиковать собственные события, которые по умолчанию -are synchronous. Это имеет несколько преимуществ - например, возможность того, что слушатель может участвовать в контексте транзакции издателя.

2.1. Простое событие приложения

Давайте создадимa simple event class - просто заполнитель для хранения данных о событии. В этом случае класс события содержит сообщение 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. Издатель

Теперь давайте создадимa publisher of that event. Издатель создает объект события и публикует его для всех, кто его слушает.

Чтобы опубликовать событие, издатель может просто ввестиApplicationEventPublisher и использовать APIpublishEvent():

@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

В качестве альтернативы, класс издателя может реализовать интерфейс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 events are synchronous - методdoStuffAndPublishAnEvent() блокируется до тех пор, пока все слушатели не закончат обработку события.

3. Создание асинхронных событий

В некоторых случаях синхронная публикация событий - это не совсем то, что нам нужно -we may need async handling of our events.

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

@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster =
          new SimpleApplicationEventMulticaster();

        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

Реализации события, издателя и слушателя остались такими же, как и раньше, но теперьthe listener will asynchronously deal with the event in a separate thread.

4. Существующие события фреймворка

Сам Spring публикует различные события из коробки. Например,ApplicationContext будет запускать различные события фреймворка. E.g. ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent и т. д.

Эти события предоставляют разработчикам приложений возможность подключиться к жизненному циклу приложения и контексту и добавить в свою собственную собственную логику, где это необходимо.

Вот краткий пример слушателя, ожидающего обновления контекста:

public class ContextRefreshedListener
  implements ApplicationListener {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent cse) {
        System.out.println("Handling context re-freshed event. ");
    }
}

Чтобы узнать больше о существующих событиях фреймворка, посмотритеour next tutorial here.

5. Слушатель событий на основе аннотаций

Начиная с Spring 4.2, прослушиватель событий не обязательно должен быть компонентом, реализующим интерфейсApplicationListener - его можно зарегистрировать в любом методеpublic управляемого bean-компонента с помощью аннотации@EventListener:

@Component
public class AnnotationDrivenContextStartedListener {
    // @Async
    @EventListener
    public void handleContextStart(ContextStartedEvent cse) {
        System.out.println("Handling context started event.");
    }
}

Как и прежде, сигнатура метода объявляет тип события, которое оно потребляет. Как и прежде, этот слушатель вызывается синхронно. Но теперь сделать его асинхронным так же просто, как добавить аннотацию@Async (не забудьтеenable Async support в приложении).

6. Поддержка Generics

Также возможно отправлять события с общей информацией в типе события.

6.1. Общее событие приложения

Let’s create a generic event type. В нашем примере класс событий содержит любое содержимое и индикатор состоянияsuccess:

public class GenericSpringEvent {
    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. Слушатель

Теперь давайте создадимa listener of that event. Мы могли бы определить слушателя, реализовав интерфейсthe ApplicationListener, как раньше:

@Component
public class GenericSpringEventListener
  implements ApplicationListener> {
    @Override
    public void onApplicationEvent(@NonNull GenericSpringEvent event) {
        System.out.println("Received spring generic event - " + event.getWhat());
    }
}

Но, к сожалению, это определение требует от нас унаследоватьGenericSpringEvent от классаApplicationEvent. Итак, в этом руководстве давайте воспользуемся прослушивателем событий на основе аннотаций, обсуждаемымpreviously.

Также возможноmake the event listener conditional, задав логическое выражение SpEL в аннотации@EventListener. В этом случае обработчик событий будет вызываться только для успешногоGenericSpringEvent из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. Издатель

Издатель события похож на описанныйabove. Но из-за стирания типа нам нужно опубликовать событие, которое разрешает параметр generics, по которому мы будем фильтровать. Например,class GenericStringSpringEvent extends GenericSpringEvent<String>.

И естьan alternative way of publishing events. Если мы вернем ненулевое значение из метода, аннотированного@EventListener в качестве результата, Spring Framework отправит нам этот результат как новое событие. Более того, мы можем публиковать несколько новых событий, возвращая их в коллекцию в результате обработки событий.

7. Связанные события транзакции

Этот параграф об использовании аннотации@TransactionalEventListener. Чтобы узнать больше об управлении транзакциями, обратитесь к руководствуTransactions with Spring and JPA.

Начиная с Spring 4.2, фреймворк предоставляет новую аннотацию@TransactionalEventListener, которая является расширением@EventListener, которая позволяет привязать слушателя события к фазе транзакции. Привязка возможна к следующим этапам транзакции:

  • AFTER_COMMIT (по умолчанию) используется для запуска события, если транзакция имеетcompleted successfully

  • AFTER_ROLLBACK - если в транзакции естьrolled back

  • AFTER_COMPLETION - если транзакция имеетcompleted (псевдоним дляAFTER_COMMIT иAFTER_ROLLBACK)

  • BEFORE_COMMIT используется для запуска события правой транзакцииbeforecommit

Вот краткий пример прослушивателя транзакционных событий:

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void handleCustom(CustomSpringEvent event) {
    System.out.println("Handling event inside a transaction BEFORE COMMIT.");
}

Этот слушатель будет вызываться только в том случае, если есть транзакция, в которой запущен производитель событий, и она вот-вот будет зафиксирована.

И, если транзакция не выполняется, событие вообще не отправляется, если мы не переопределим это, установив для атрибутаfallbackExecution значениеtrue.

8. Заключение

В этом кратком руководстве мы рассмотрели основыdealing with events in Spring - создание простого настраиваемого события, его публикация и последующая обработка в прослушивателе.

Мы также кратко рассмотрели, как включить асинхронную обработку событий в конфигурации.

Затем мы узнали об улучшениях, представленных в Spring 4.2, таких как прослушиватели на основе аннотаций, улучшенная поддержка обобщенных элементов и привязка событий к этапам транзакции.

Как всегда, доступен код, представленный в этой статьеover on Github. Это проект, основанный на Maven, поэтому его легко импортировать и запускать как есть.