Статьи

События лучшего приложения в Spring Framework 4.2


Первоначально написано Стефаном Николлом в блоге 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_COMMITAFTER_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>

Как всегда, мы приветствуем отзывы сообщества, пожалуйста, попробуйте эти функции и сообщите нам, если у вас возникнут какие-либо проблемы.