Весняні події

1. Огляд

У цій статті ми обговоримо, як використовувати події навесні .

Події є однією з функцій, що не враховуються, але також однією з найбільш корисних. І, як і багато іншого навесні, публікація подій є однією з можливостей ApplicationContext.

Існує кілька простих вказівок:

  • подія повинна розширити ApplicationEvent
  • видавець повинен ввести об'єкт ApplicationEventPublisher
  • слухач повинен реалізувати інтерфейс ApplicationListener

2. Спеціальна подія

Spring дозволяє нам створювати та публікувати власні події, які за замовчуванням є синхронними . Це має кілька переваг - наприклад, наприклад, можливість слухача брати участь у контексті транзакції видавця.

2.1. Проста подія програми

Давайте створимо простий клас подій - просто заповнювач для зберігання даних про подію. У цьому випадку клас подій містить повідомлення String:

public class CustomSpringEvent extends ApplicationEvent { private String message; public CustomSpringEvent(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } }

2.2. Видавець

Тепер давайте створимо видавця цієї події . Видавець конструює об’єкт події та публікує його всім, хто слухає.

Щоб опублікувати подію, видавець може просто ввести ApplicationEventPublisher і скористатися API pubEvent () :

@Component public class CustomSpringEventPublisher { @Autowired private ApplicationEventPublisher applicationEventPublisher; public void publishCustomEvent(final String message) { System.out.println("Publishing custom event. "); CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message); applicationEventPublisher.publishEvent(customSpringEvent); } }

Крім того, клас видавця може реалізувати інтерфейс ApplicationEventPublisherAware - це також внесе видавець події під час запуску програми. Зазвичай простіше просто вколоти видавцю @Autowire.

2.3. Слухач

Нарешті, давайте створимо слухача.

Єдина вимога до слухача - бути компонентом і впровадити інтерфейс ApplicationListener :

@Component public class CustomSpringEventListener implements ApplicationListener { @Override public void onApplicationEvent(CustomSpringEvent event) { System.out.println("Received spring custom event - " + event.getMessage()); } }

Зверніть увагу, як наш власний прослуховувач параметризований із загальним типом спеціальної події, що робить метод onApplicationEvent () безпечним для типу. Це також дозволяє уникнути необхідності перевіряти, чи об’єкт є екземпляром певного класу подій, та здійснювати його приведення.

І, як уже обговорювалося - за замовчуванням події весни синхронні - метод doStuffAndPublishAnEvent () блокує, поки всі слухачі не закінчать обробку події.

3. Створення асинхронних подій

У деяких випадках синхронна публікація подій насправді не є тим, що ми шукаємо - нам може знадобитися асинхронна обробка наших подій .

Ви можете увімкнути це в конфігурації, створивши компонент ApplicationEventMulticaster із виконавцем; для наших цілей тут SimpleAsyncTaskExecutor добре працює:

@Configuration public class AsynchronousSpringEventsConfig { @Bean(name = "applicationEventMulticaster") public ApplicationEventMulticaster simpleApplicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); return eventMulticaster; } }

Реалізація події, видавця та слухача залишаються такими ж, як і раніше - але тепер слухач буде асинхронно обробляти подію в окремому потоці .

4. Існуючі рамкові події

Сама «Весна» публікує різні події нестандартно. Наприклад, ApplicationContext запускатиме різні фреймворкові події. Наприклад, ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent тощо.

Ці події надають розробникам додатків можливість зачепити життєвий цикл програми та контекст та додати у власній власній логіці, де це необхідно.

Ось короткий приклад слухача, який прослуховує оновлення контексту:

public class ContextRefreshedListener implements ApplicationListener { @Override public void onApplicationEvent(ContextRefreshedEvent cse) { System.out.println("Handling context re-freshed event. "); } }

Щоб дізнатись більше про існуючі фреймворкові події, перегляньте наш наступний підручник тут.

5. Слухач подій, керований анотаціями

Починаючи з весни 4.2, прослуховувач подій не повинен бути компонентом, що реалізує інтерфейс ApplicationListener - його можна зареєструвати в будь-якому загальнодоступному методі керованого компонента через анотацію @EventListener :

@Component public class AnnotationDrivenEventListener { @EventListener public void handleContextStart(ContextStartedEvent cse) { System.out.println("Handling context started event."); } }

Як і раніше, підпис методу оголошує споживаний ним тип події.

За замовчуванням слухач викликається синхронно. Однак ми можемо легко зробити його асинхронним, додавши анотацію @Async . Однак ми повинні пам’ятати про те, щоб увімкнути підтримку Async у програмі.

6. Підтримка дженериків

Також можливо відправляти події із загальною інформацією у типі події.

6.1. Загальна подія програми

Let's create a generic event type. In our example, the event class holds any content and a success status indicator:

public class GenericSpringEvent { private T what; protected boolean success; public GenericSpringEvent(T what, boolean success) { this.what = what; this.success = success; } // ... standard getters }

Notice the difference between GenericSpringEvent and CustomSpringEvent. We now have the flexibility to publish any arbitrary event and it's not required to extend from ApplicationEvent anymore.

6.2. A Listener

Now let’s create a listener of that event. We could define the listener by implementing the ApplicationListener interface like before:

@Component public class GenericSpringEventListener implements ApplicationListener
    
      { @Override public void onApplicationEvent(@NonNull GenericSpringEvent event) { System.out.println("Received spring generic event - " + event.getWhat()); } }
    

But unfortunately, this definition requires us to inherit GenericSpringEvent from the ApplicationEvent class. So for this tutorial, let's make use of an annotation-driven event listener discussed previously.

It is also possible to make the event listener conditional by defining a boolean SpEL expression on the @EventListener annotation. In this case, the event handler will only be invoked for a successful GenericSpringEvent of String:

@Component public class AnnotationDrivenEventListener { @EventListener(condition = "#event.success") public void handleSuccessful(GenericSpringEvent event) { System.out.println("Handling generic event (conditional)."); } }

The Spring Expression Language (SpEL) is a powerful expression language that's covered in detail in another tutorial.

6.3. A Publisher

The event publisher is similar to the one described above. But due to type erasure, we need to publish an event that resolves the generics parameter we would filter on. For example, class GenericStringSpringEvent extends GenericSpringEvent.

And there's an alternative way of publishing events. If we return a non-null value from a method annotated with @EventListener as the result, Spring Framework will send that result as a new event for us. Moreover, we can publish multiple new events by returning them in a collection as the result of event processing.

7. Transaction Bound Events

This paragraph is about using the @TransactionalEventListener annotation. To learn more about transaction management check out the Transactions with Spring and JPA tutorial.

Since Spring 4.2, the framework provides a new @TransactionalEventListener annotation, which is an extension of @EventListener, that allows binding the listener of an event to a phase of the transaction. Binding is possible to the following transaction phases:

  • AFTER_COMMIT (default) is used to fire the event if the transaction has completed successfully
  • AFTER_ROLLBACK – if the transaction has rolled back
  • AFTER_COMPLETION – if the transaction has completed (an alias for AFTER_COMMIT and AFTER_ROLLBACK)
  • BEFORE_COMMIT is used to fire the event right before transaction commit

Here's a quick example of transactional event listener:

@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) public void handleCustom(CustomSpringEvent event) { System.out.println("Handling event inside a transaction BEFORE COMMIT."); }

This listener will be invoked only if there's a transaction in which the event producer is running and it's about to be committed.

And, if no transaction is running the event isn’t sent at all unless we override this by setting fallbackExecution attribute to true.

8. Conclusion

У цьому короткому навчальному посібнику ми ознайомилися з основами роботи з подіями навесні - створивши просту власну подію, опублікувавши її, а потім обробивши в слухачі.

Ми також коротко розглянули, як увімкнути асинхронну обробку подій у конфігурації.

Потім ми дізналися про вдосконалення, представлені навесні 4.2, такі як прослуховувачі, керовані анотаціями, краща підтримка загальних джерел та події, прив’язані до фаз транзакцій.

Як завжди, код, представлений у цій статті, доступний на Github. Це проект на базі Maven, тому його слід легко імпортувати та запускати як є.