Вступление
Обмен событиями в приложении стал неотъемлемой частью многих приложений, и, к счастью, 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 . |