Первоначально написано Стефаном Николлом в блоге Spring
События приложения доступны с самого начала среды Spring как средство для слабосвязанных компонентов для обмена информацией. Одним из наиболее известных случаев использования событий приложений является следующее:
@Component
public class MyListener
implements ApplicationListener<ContextRefreshedEvent> {
public void onApplicationEvent(ContextRefreshedEvent event) {
...
}
}
Это позволяет MyListener
получать уведомления, когда контекст обновляется, и его можно использовать для запуска произвольного кода, когда контекст приложения полностью запущен.
В Spring Framework 4.2 мы пересмотрели инфраструктуру событий в трех основных областях, которые я собираюсь объяснить в этом посте.
Поддержка дженериков
Теперь можно определить вашу ApplicationListener
реализацию с помощью вложенной обобщенной информации в типе события, например:
public class MyListener
implements ApplicationListener<MyEvent<Order>> { ... }
При отправке события подпись вашего слушателя используется для определения того, соответствует ли оно указанному входящему событию.
Из-за стирания типа вам нужно опубликовать событие, которое разрешает универсальный параметр, по которому вы будете фильтровать, что-то вроде этого
MyOrderEvent extends MyEvent<Order>
. Могут быть и другие обходные пути, и мы будем рады вернуться к алгоритму сопоставления подписей, если сообщество сочтет это целесообразным.
Приемник событий, управляемый аннотациями
Самая большая новая функция — это поддержка обработчиков событий на основе аннотаций, аналогичная нашей недавней работе с конечными точками JMS и AMQP в Spring Framework 4.1. Короче говоря, теперь можно просто аннотировать метод управляемого компонента с помощью @EventListener
автоматической регистрации ApplicationListener
соответствующей сигнатуры метода. Наш пример выше можно переписать следующим образом:
@Component
public class MyListener {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
...
}
}
@EventListener
является основной аннотацией, которая прозрачно обрабатывается так же, как @Autowired
и другие: дополнительная конфигурация с java config не требуется, и существующий <context:annotation-driven/>
элемент обеспечивает ее полную поддержку.
Сигнатура метода определяет интересующий вас тип события. Также возможно определить выражение SpEL, которое должно совпадать для обработки события. Например, рассмотрим следующее событие:
public class OrderCreatedEvent implements CreationEvent<Order> { ... }
private boolean awesome;
public boolean isAwesome() { return this.awesome; }
....
}
Следующий пример демонстрирует слушатель событий , который будет вызываться только для удивительного CreationEvent
из Order
(то есть , если awesome
флаг true
):
@Component
public class MyComponent {
@EventListener(condition = "#creationEvent.awesome")
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
Как видно из приведенного выше примера, аргументы метода предоставляются через их имена, если такая информация может быть обнаружена. Выражение условия также предоставляет «корневую» переменную с raw
ApplicationEvent
(#root.event
) и фактическими аргументами метода (#root.args
).
Публикация событий
Вы можете определить void
тип невозврата для любого метода, помеченного как @EventListener
. Если вы вернете ненулевое null
значение в результате обработки определенного события, мы отправим этот результат как новое событие для вас.
Вы, возможно, заметили, что наше OrderCreatedEvent
не распространяется от ApplicationEvent
; мы почувствовали, что пришло время дать вам возможность опубликовать любое произвольное событие и не заставлять вас выходить за пределы ApplicationEvent
. ApplicationEventPublisher
Интерфейс был расширен , чтобы позволить вам публиковать любой объект; когда указанный объект не является ApplicationEvent
, мы завернем его PayloadApplicationEvent
для вас. Помните об этом, если вы хотите прослушать такое произвольное событие, используя обычную ApplicationListener
реализацию.
В следующем примере показано, как вы можете использовать ApplicationEventPublisher
для отправки OrderCreatedEvent
:
@Component
public class MyComponent {
private final ApplicationEventPublisher publisher;
@Autowired
public MyComponent(ApplicationEventPublisher publisher) { ... }
public void createOrder(Order order) {
// ....
this.publisher.publishEvent(new OrderCreatedEvent(order));
}
}
События, связанные с транзакцией
Другое популярное улучшение — это возможность привязать слушателя события к фазе транзакции. Типичным примером является обработка события, когда транзакция успешно завершена: это позволяет использовать события более гибко, когда результат текущей транзакции действительно имеет значение для слушателя.
Spring Framework в настоящее время структурирован таким образом, что контекст не знает о поддержке транзакций, и мы, очевидно, не хотели отклоняться от этого очень здравого принципа, поэтому мы создали открытую инфраструктуру, позволяющую регистрировать дополнительные компоненты и влиять на способ слушатели событий созданы.
Модуль транзакций реализует EventListenerFactory
поиск новой @TransactionalEventListener
аннотации. Когда он присутствует, расширенный прослушиватель событий, который знает о транзакции, регистрируется вместо значения по умолчанию.
Давайте повторно воспользуемся нашим примером выше и перепишем его так, чтобы событие создания заказа было обработано только после успешного завершения транзакции, в которой работает производитель:
@Component
public class MyComponent {
@TransactionalEventListener(condition = "#creationEvent.awesome")
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
Не так много, чтобы увидеть, верно? @TransactionalEventListener
является обычным @EventListener
и также предоставляет TransactionPhase
, по умолчанию AFTER_COMMIT
. Вы также можете подключить другие этапы сделки ( BEFORE_COMMIT
, AFTER_ROLLBACK
и AFTER_COMPLETION
это всего лишь псевдоним , AFTER_COMMIT
а AFTER_ROLLBACK
).
По умолчанию, если никакая транзакция не выполняется, событие вообще не отправляется, поскольку мы, очевидно, не можем выполнить запрошенную фазу, но есть fallbackExecution
атрибут, @TransactionalEventListener
который сообщает Spring, чтобы немедленно вызывать прослушиватель, если транзакции нет.
Попробуйте!
Если вы хотите попробовать это до первого этапа выпуска 4.2, получите ночную сборку SNAPSHOT через наш репозиторий моментальных снимков . Вы также можете создать пример проекта, используя start.spring.io, используя последнюю сборку снимка Spring Boot, или, если вам лень, вы можете скопировать / вставить это в вашу оболочку:
$ curl https://start.spring.io/starter.tgz -d artifactId=events-demo \
-d baseDir=events-demo -d bootVersion=1.2.2.BUILD-SNAPSHOT | tar -xzvf -
И обновить проект для использования Spring Framework 4.2.0.BUILD-SNAPSHOT
<properties>
...
<spring.version>4.2.0.BUILD-SNAPSHOT</spring.version>
</properties>
Как всегда, мы приветствуем отзывы сообщества, пожалуйста, попробуйте эти функции и сообщите нам, если у вас возникнут какие-либо проблемы.