Вступление
Как вы, вероятно, уже знаете (например, из моего предыдущего поста в блоге ), больше не нужно создавать отдельный класс, реализующий ApplicationListener
с методом onApplicationEvent
чтобы иметь возможность реагировать на события приложения (как из самой Spring Framework, так и из наших собственных событий домена). Начиная с Spring 4.2 была добавлена поддержка обработчиков событий, управляемых аннотациями. Достаточно использовать @EventListener
на уровне метода, который автоматически зарегистрирует соответствующий ApplicationListener
:
1
2
3
4
|
@EventListener public void blogAdded(BlogAddedEvent blogAddedEvent) { externalNotificationSender.blogAdded(blogAddedEvent); } |
Обратите внимание, что использование доменных объектов в событиях имеет существенный недостаток и не является лучшей идеей во многих ситуациях. Псевдодоменные объекты в примерах кода использовались, чтобы не вносить ненужную сложность.
События, связанные с транзакцией
Просто и компактно. Для «стандартных» событий все выглядит отлично, но в некоторых случаях необходимо выполнить некоторые операции (обычно асинхронные) сразу после того, как транзакция была зафиксирована (или откатана). Что тогда? Можно ли использовать новый механизм?
Бизнес-требования
Во-первых, небольшое отступление — требования бизнеса. Давайте представим себе супер-необычный сервис агрегации блогов. Событие генерируется каждый раз, когда добавляется новый блог. Подписанные пользователи могут получить SMS или push-уведомление. Событие может быть опубликовано после того, как объект блога планируется сохранить в базе данных. Однако в случае сбоя фиксации / сброса (нарушение ограничений базы данных, проблема с генератором идентификаторов и т. Д.) Вся транзакция БД будет откатываться. Много недовольных пользователей с испорченными уведомлениями появятся у дверей …
Технические неполадки
В современном подходе к управлению транзакциями транзакции настраиваются декларативно (например, с @Transactional
аннотации @Transactional
), и фиксация запускается в конце области транзакции (например, в конце метода). В целом это очень удобно и намного менее подвержено ошибкам (чем программный подход). С другой стороны, фиксация (или откат) выполняется автоматически вне нашего кода, и мы не можем реагировать «классическим способом» (т.е. публиковать событие в следующей строке после transaction.commit()
).
Старая школа реализации
Одно из возможных решений для Spring (и очень элегантное) было представлено незаменимым Томеком Нуркевичем . Он использует TransactionSynchronizationManager
для регистрации синхронизации транзакции для текущего потока. Например:
01
02
03
04
05
06
07
08
09
10
11
12
|
@EventListener public void blogAddedTransactionalOldSchool(BlogAddedEvent blogAddedEvent) { //Note: *Old school* transaction handling before Spring 4.2 - broken in not transactional context TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { internalSendBlogAddedNotification(blogAddedEvent); } }); } |
Переданный код выполняется в нужном месте рабочего процесса транзакции Spring (в этом случае «сразу» после коммита).
Чтобы обеспечить поддержку выполнения в нетранзакционном контексте (например, в интеграционных тестовых случаях, которые не могли заботиться о транзакциях), его можно расширить до следующей формы, чтобы не вызывать сбоев с java.lang.IllegalStateException: Transaction synchronization is not active
исключением:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
@EventListener public void blogAddedTransactionalOldSchool( final BlogAddedEvent blogAddedEvent) { //Note: *Old school* transaction handling before Spring 4.2 //"if" to not fail with "java.lang.IllegalStateException: Transaction synchronization is not active" if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { internalSendBlogAddedNotification(blogAddedEvent); } }); } else { log.warn( "No active transaction found. Sending notification immediately." ); externalNotificationSender.newBlogTransactionalOldSchool(blogAddedEvent); } } |
С этим изменением в случае отсутствия активной транзакции предоставленный код выполняется немедленно. Пока работает отлично, но давайте попробуем добиться того же с помощью обработчиков событий, управляемых аннотациями, в Spring 4.2.
Spring 4.2+ реализация
В дополнение к @EventListener
Spring 4.2 предоставляет еще одну аннотацию @TransactionalEventListener
.
1
2
3
4
|
@TransactionalEventListener public void blogAddedTransactional(BlogAddedEvent blogAddedEvent) { externalNotificationSender.newBlogTransactional(blogAddedEvent); } |
Выполнение может быть привязано к стандартным этапам транзакции: до / после принятия, после отката или после завершения (как принятие, так и откат). По умолчанию оно обрабатывает событие, только если оно было опубликовано в рамках транзакции. В другом случае событие отбрасывается.
Для поддержки выполнения в falbackExecution
контексте может использоваться флаг falbackExecution
. Если установлено значение «истина», событие обрабатывается немедленно, если транзакция не выполняется.
1
2
3
4
|
@TransactionalEventListener (fallbackExecution = true ) public void blogAddedTransactional(BlogAddedEvent blogAddedEvent) { externalNotificationSender.newBlogTransactional(blogAddedEvent); } |
Резюме
Представленные в Spring 4.2 обработчики событий на основе аннотаций продолжают тенденцию к сокращению стандартного кода в приложениях, основанных на Spring (Boot). Не нужно вручную создавать реализации ApplicationListener
, не нужно напрямую использовать TransactionSynchronizationManager
— только одна аннотация с правильной конфигурацией. Другая сторона медали в том, что все слушатели событий немного сложнее найти, особенно если в нашем монолитном приложении их десятки (хотя их легко сгруппировать). Конечно, новый подход — это только вариант, который может быть полезен в данном случае использования или нет. Тем не менее, еще один кусочек весеннего (загрузочного) волшебства попал в наши системы. Но, может быть, сопротивление бесполезно?
Сопротивление бесполезно?
Источник: http://en.memory-alpha.wikia.com/wiki/Borg_cube
Обратите внимание, что Spring Framework 4.2 является зависимостью по умолчанию Spring Boot 1.3 (на момент написания 1.3.0.M5 доступен). Кроме того, можно вручную обновить версию Spring Framework в Gradle / Maven для Spring Boot 1.2.5 — она должна работать в большинстве случаев.
- Примеры кода доступны на GitHub .
Кстати, написание примеров для этого поста в блоге дало мне первую реальную возможность использовать новую тестовую систему управления транзакциями, представленную в Spring 4.1 (в прошлом я упоминал об этом только во время моих учебных занятий Spring). Возможно, скоро напишу об этом подробнее.
Ссылка: | Упрощенная обработка связанных с асинхронными транзакциями событий в Spring 4.2 от нашего партнера по JCG Марцина Заячковского из блога Solid Soft . |