Обмен событиями в приложении стал неотъемлемой частью многих приложений, и, к счастью, Spring предоставляет полную инфраструктуру для переходных событий (*). Недавний рефакторинг событий, связанных с транзакциями, дал мне повод проверить и попрактиковаться в новых обработчиках событий на основе аннотаций, представленных в Spring 4.2. Посмотрим, что можно получить.
(*) — для постоянных событий в Spring-приложении Duramen может быть решением, которое стоит посмотреть.
По старому
Чтобы получить уведомление о событии (как Spring события и пользовательский домен событий) компонент , реализующем ApplicationListener
с onApplicationEvent
должно быть создано.
@Component
class OldWayBlogModifiedEventListener implements
ApplicationListener<OldWayBlogModifiedEvent> {
(...)
@Override
public void onApplicationEvent(OldWayBlogModifiedEvent event) {
externalNotificationSender.oldWayBlogModified(event);
}
}
Это работает нормально, но для каждого события должен быть создан новый класс, который генерирует шаблонный код.
Кроме того, наше событие должно расширить ApplicationEvent
класс — базовый класс для всех событий приложения в Spring.
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
public void blogModified(BlogModifiedEvent blogModifiedEvent) {
externalNotificationSender.blogModified(blogModifiedEvent);
}
Под капотом Spring создаст ApplicationListener
экземпляр для события с типом, взятым из аргумента метода. Количество аннотированных методов в одном классе не ограничено — все связанные обработчики событий могут быть сгруппированы в один класс.
Условная обработка событий
Для того, чтобы сделать @EventListener
еще более интересным есть возможность обрабатывать только те события данного типа , которые удовлетворяют заданное условие (S) , написанный в SPEL . Давайте предположим следующий класс событий:
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
можно использовать параметр:
@EventListener(condition = "#blogModifiedEvent.importantChange")
public void blogModifiedSpEL(BlogModifiedEvent blogModifiedEvent) {
externalNotificationSender.blogModifiedSpEL(blogModifiedEvent);
}
Расслабленная иерархия типов событий
Исторически ApplicationEventPublisher
была только возможность публиковать объекты, которые унаследованы после ApplicationEvent. Начиная с Spring 4.2, интерфейс был расширен для поддержки любых типов объектов. В этом случае объект оборачивается PayloadApplicationEvent
и отправляется через.
//base class with Blog field - no need to extend `ApplicationEvent`
class BaseBlogEvent {}
class BlogModifiedEvent extends BaseBlogEvent {}
//somewhere in the code
ApplicationEventPublisher publisher = (...); //injected
publisher.publishEvent(new BlogModifiedEvent(blog)); //just plain instance of the event
Это изменение делает публикацию событий еще проще. Однако, с другой стороны, без внутренней добросовестности (например, с интерфейсом маркера для всех событий нашего домена) это может усложнить отслеживание событий, особенно в более крупных приложениях.
Публикация событий в ответ на
Еще одна приятная вещь @EventListener
— это тот факт, что в случае не возвращаемого типа возврата Spring автоматически публикует возвращаемое событие.
@EventListener
public BlogModifiedResponseEvent blogModifiedWithResponse(BlogModifiedEvent blogModifiedEvent) {
externalNotificationSender.blogModifiedWithResponse(blogModifiedEvent);
return new BlogModifiedResponseEvent(
blogModifiedEvent.getBlog(), BlogModifiedResponseEvent.Status.OK);
}
Асинхронная обработка событий
Как справедливо предположил Радек Грэбски, также стоит упомянуть, что @EventListener
его можно легко комбинировать с @Async
аннотацией для обеспечения асинхронной обработки событий. Код в конкретном слушателе событий не блокирует ни выполнение основного кода, ни обработку другими слушателями.
@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 — она должна работать в большинстве случаев.