Статьи

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

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