Статьи

Управляемые аннотациями слушатели событий в Spring 4.2

Вступление

Обмен событиями в приложении стал неотъемлемой частью многих приложений, и, к счастью, Spring предоставляет полную инфраструктуру для переходных событий (*). Недавний рефакторинг событий, связанных с транзакциями, дал мне повод проверить на практике новые обработчики событий на основе аннотаций, представленные в Spring 4.2. Посмотрим, что можно получить.

(*) — для постоянных событий в Spring-приложении Duramen может быть решением, которое стоит посмотреть

По старому

Чтобы получить уведомление о событии (как событие Spring, так и событие настраиваемого домена), необходимо создать компонент, реализующий ApplicationListener с onApplicationEvent .

01
02
03
04
05
06
07
08
09
10
11
@Component
class OldWayBlogModifiedEventListener implements
                        ApplicationListener<OldWayBlogModifiedEvent> {
 
    (...)
 
    @Override
    public void onApplicationEvent(OldWayBlogModifiedEvent event) {
        externalNotificationSender.oldWayBlogModified(event);
    }
}

Это работает нормально, но для каждого события должен быть создан новый класс, который генерирует шаблонный код.

Кроме того, наше событие должно расширить класс ApplicationEvent — базовый класс для всех событий приложения в Spring.

01
02
03
04
05
06
07
08
09
10
class OldWayBlogModifiedEvent extends ApplicationEvent {
 
    public OldWayBlogModifiedEvent(Blog blog) {
        super(blog);
    }
 
    public Blog getBlog() {
        return (Blog)getSource();
    }
}

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

Кстати, ExternalNotificationSender в этом примере является экземпляром класса, который отправляет внешние уведомления зарегистрированным пользователям (например, по электронной почте, SMS или Slack).

Приемник событий, управляемый аннотациями

Начиная с Spring 4.2, чтобы получать уведомления о новом событии, достаточно аннотировать метод в любом компоненте Spring с @EventListener аннотации @EventListener .

1
2
3
4
@EventListener
    public void blogModified(BlogModifiedEvent blogModifiedEvent) {
        externalNotificationSender.blogModified(blogModifiedEvent);
    }

Под капотом Spring создаст экземпляр ApplicationListener для события с типом, взятым из аргумента метода. Количество аннотированных методов в одном классе не ограничено — все связанные обработчики событий могут быть сгруппированы в один класс.

Условная обработка событий

Чтобы сделать @EventListener еще более интересным, есть возможность обрабатывать только те события данного типа, которые удовлетворяют заданным условиям, написанным в SpEL . Давайте предположим следующий класс событий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BlogModifiedEvent {
 
    private final Blog blog;
    private final boolean importantChange;
 
    public BlogModifiedEvent(Blog blog) {
        this(blog, false);
    }
 
    public BlogModifiedEvent(Blog blog, boolean importantChange) {
        this.blog = blog;
        this.importantChange = importantChange;
    }
 
    public Blog getBlog() {
        return blog;
    }
 
    public boolean isImportantChange() {
        return importantChange;
    }
}

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

Для генерации события только для важных изменений можно использовать параметр condition :

1
2
3
4
@EventListener(condition = "#blogModifiedEvent.importantChange")
    public void blogModifiedSpEL(BlogModifiedEvent blogModifiedEvent) {
        externalNotificationSender.blogModifiedSpEL(blogModifiedEvent);
    }

Расслабленная иерархия типов событий

Исторически ApplicationEventPublisher имел возможность только публиковать объекты, унаследованные после ApplicationEvent. Начиная с Spring 4.2, интерфейс был расширен для поддержки любых типов объектов. В этом случае объект оборачивается в PayloadApplicationEvent и отправляется через.

1
2
3
4
//base class with Blog field - no need to extend `ApplicationEvent`
class BaseBlogEvent {}
 
class BlogModifiedEvent extends BaseBlogEvent {}
1
2
3
4
//somewhere in the code
ApplicationEventPublisher publisher = (...);    //injected
 
publisher.publishEvent(new BlogModifiedEvent(blog)); //just plain instance of the event

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

Публикация событий в ответ на

Еще одна приятная вещь с @EventListener — это тот факт, что в случае не возвращаемого значения тип возврата Spring автоматически публикует возвращаемое событие.

1
2
3
4
5
6
@EventListener
    public BlogModifiedResponseEvent blogModifiedWithResponse(BlogModifiedEvent blogModifiedEvent) {
        externalNotificationSender.blogModifiedWithResponse(blogModifiedEvent);
        return new BlogModifiedResponseEvent(
            blogModifiedEvent.getBlog(), BlogModifiedResponseEvent.Status.OK);
    }

Асинхронная обработка событий

Обновлено Как справедливо предположил Радек Грэбски, также стоит упомянуть, что @EventListener можно легко комбинировать с аннотацией @Async для обеспечения асинхронной обработки событий. Код в конкретном слушателе событий не блокирует ни выполнение основного кода, ни обработку другими слушателями.

1
2
3
4
5
6
@Async    //Remember to enable asynchronous method execution
              //in your application with @EnableAsync
    @EventListener
    public void blogAddedAsync(BlogAddedEvent blogAddedEvent) {
        externalNotificationSender.blogAdded(blogAddedEvent);
    }

Чтобы это работало, требуется только включить асинхронное выполнение метода в целом в контексте / приложении Spring с помощью @EnableAsync .

Резюме

Слушатели событий, управляемые аннотациями, представленные в Spring 4.2, продолжают тенденцию к сокращению стандартного кода в приложениях, основанных на Spring (Boot). Новый подход выглядит интересным, особенно для небольших приложений с небольшим количеством событий, где затраты на обслуживание ниже. В мире вездесущей Весенней (Загрузочной) магии стоит вспомнить, что с большой силой приходит большая ответственность.

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

Обратите внимание, что Spring Framework 4.2 является зависимостью по умолчанию Spring Boot 1.3 (на момент написания 1.3.0.M5 доступен). В качестве альтернативы можно вручную обновить версию Spring Framework в Gradle / Maven для Spring Boot 1.2.5 — она ​​должна работать в большинстве случаев.

Ссылка: Слушатели событий, управляемые аннотациями весной 4.2, от нашего партнера по JCG Марцина Заячковского в блоге Solid Soft .