Spring Application Events
Understanding Spring Application Events, built-in events, and custom events.
Why use Spring Events?
There are situations when it’s required to bind some actions on a specific level on application startup, to be able to know when some actions are completed we can subscribe to the built-in events raised by spring application context and do the required actions.
On the other hand, in any spring application may rise the need for a specific domain to generate some specific events that should be handled by other domains/modules without any concern about how they will be handled. Spring framework provides a built-in mechanism that comes out of the box with Spring Application Events.
Why should I Use Events Instead of Direct Method Calls?
Direct method calls and events fit different situations. Usually, a method call is used when a method should be executed in the same transaction, or a set of operations that should be atomic, or the code should be executed in the same processing thread. On the other hand, events fit the case when we want the processing to be done in another thread, or in another module/domain (for example: on some task completion we want to send an email).
Using the spring event-based communication model, the sender publishes an event without knowing who the receiver might be. There may be more than one receiver component, also, the receiver doesn’t need to know who is the publisher is. A receiver can listen to multiple events at the same time. In this way, the sender and receiver components are loosely coupled, which makes testing the application easier.
In this post the following topics will be described:
- Spring Core built-in Application Events
- SpringBoot built-in Application Events
- Custom Spring Application Events and how to use them
Spring Core built-in Application Events
Core Events Overview
Spring core framework provides application-level event firing on loading the beans. The ApplicationContext publishes certain types of events on different types of loaded beans.
The pattern behind Spring Application Events firing and event listening is Observer Design Pattern.
To have a better idea of how they work let’s see built-in application events and when they are raised:
ContextRefreshedEvent — is published when the ApplicationContext is either initialized or refreshed. This can also be raised using the refresh() method on the ConfigurableApplicationContext interface.
ContextStartedEvent — is published when the ApplicationContext is started using the start() method on the ConfigurableApplicationContext interface. At this stage you can poll your database or you can restart any stopped application after receiving this event.
ContextStoppedEvent — is published when the ApplicationContext is stopped using the stop() method on the ConfigurableApplicationContext interface. You can do required housekeep work after receiving this event.
ContextClosedEvent — is published when the ApplicationContext is closed using the close() method on the ConfigurableApplicationContext interface. A closed context reaches its end of life; it cannot be refreshed or restarted.
In the following image see the order of event raising:
As you may notice the ContextRefreshedEvent is raised twice at different stages of application startup when the context is initialized and on context refresh.
Under the hood, each implementation of ApplicationContext extends AbstractApplicationContext which implements common context functionality and also encapsulates the core application events raising.
These methods are invoked by the different implementations of ApplicationContext, and you can see at what stage a specific event is raised.
How to listen to the core built-in events?
Before Spring 4.2 to be able to listen to an event you should implement the ApplicationListener<E extends ApplicationEvent> and register to Application Config as @Component or defining as @Bean in your configuration, Java config, or XML config.
Starting with Spring 4.2 now possible to simply annotate a method of a managed bean with @EventListener to automatically register an ApplicationListener, and it doesn’t require additional configs or bean definition.
You can listen for an event by just telling to @EventListener(classes = ContextStartedEvent.class) the classes which you want to trigger the method invocation or telling the method the object that should be passed
In the following image see the execution logs of the events raised order:
SpringBoot built-in Application Events
Springboot built-in Application Events Overview
Let’s see spring-boot built-in application events and when they are raised:
ApplicationStartingEvent — is sent at the start of a run but before any processing, except for the registration of listeners and initializers.
ApplicationEnvironmentPreparedEvent — is sent when the Environment to be used in the context is known but before the context is created.
ApplicationContextInitializedEvent — is sent when the ApplicationContext is prepared and ApplicationContextInitializers have been called but before any bean definitions are loaded.
ApplicationPreparedEvent — is sent just before the refresh is started but after bean definitions have been loaded.
ApplicationStartedEvent — is sent after the context has been refreshed but before any application and command-line runners have been called.
AvailabilityChangeEvent — is sent right after with LivenessState.CORRECT to indicate that the application is considered as live and after with ReadinessState.ACCEPTING_TRAFFIC to indicate that the application is ready to service requests.
ApplicationReadyEvent — is sent after any application and command-line runners have been called.
ApplicationFailedEvent — is sent if there is an exception on startup.
The above list only includes SpringApplicationEvents that are tied to a SpringApplication. In addition to these, the following events are also published after ApplicationPreparedEvent and before ApplicationStartedEvent:
A WebServerInitializedEvent is sent after the WebServer is ready. ServletWebServerInitializedEvent and ReactiveWebServerInitializedEvent are the servlet and reactive variants respectively.
A ContextRefreshedEvent is sent when an ApplicationContext is refreshed.
In the following image see the order of event raising:
Some events are triggered before the ApplicationContext is created, so we cannot register a listener on those as a @Bean. We can register listeners for these events by adding the listener manually:
Listen to the Springboot built-in events
If you want those listeners to be registered automatically, regardless of the way the application is created, you can add a META-INF/spring.factories file to your project and reference your listener(s) by using the org.springframework.context.ApplicationListener key, as shown in the following example:
org.springframework.context.ApplicationListener=com.semotpan.springboot.application.events.ApplicationStartingEventHandler
Once we make sure that our event listener is registered properly, we can listen to all of Spring Boot’s SpringApplicationEvents. Let’s have a look at the execution logs during application startup.
You may notice the WebServerInitializedEvent is raised, because as dependency is added
which initializes the web context.
Custom Spring Application Events and how to use them
Application Events Creation
To create a custom event, before Spring 4.2, you’ll have to extend ApplicationEvent
The source which is being passed to super() should be the object on which the event occurred initially or, an associated object with the event.
Starting with Spring 4.2 a custom event doesn’t require anymore to extend the ApplicationEvent interface.
Spring will automatically wrap it in a PayloadApplicationEvent.
Usually, the naming convention for an event is Domain name on which event occurred, the action that triggered the event, which is in the past tense, and the event as a suffix. Examples: UserCreatedEvent, PostCreatedEvent, PostUpdatedEvent.
Publishing Application Events
To send an application event we can use ApplicationEventPublisher, which is provided by Spring Application Context
if the event extends ApplicationEvent, publishing looks like:
Listening to Application Events
There are two ways to define a listener. We can either use the @EventListener annotation or implement the ApplicationListener interface. In either case, the listener class has to be managed by Spring
No additional configuration is required with annotation-driven configuration enabled.
We can bind a listener to as many events as we want
The following example shows how to define an Application Listener by extending the ApplicationListener interface
Conditional Application Listeners
Spring allows listeners to be triggered only in certain circumstances if we specify a SpEL condition
In the example above, the listener will be triggered with PostCreatedEvent only when the #event.status.name() is equal to ‘UPDATE’.
The event will only be handled if the expression evaluates to one of the following values: “true”, “1”, “on”, “yes”.
Synchronous Application Listeners
Listening for an event with @EventListener is done synchronously, this means that the processing is done in the same thread. If the method that raised the event is executed in a transaction, and the event handling fails then the whole transaction will fail.
Also, it’s possible to raise a new event from an event listener by returning a new event from the event listener.
Asynchronous Application Listeners
As mentioned in previous paragraphs, the events by default are handled in a synchronous way. This means that the publisher thread blocks until all the listeners have finished processing the event. When blocking of the thread isn’t wanted we need to enable async handling of our events. To achieve that, we have to annotate the event handler with @Async and expose an Executor into the spring context.
Configuration for enabling the async mode, and, also, a custom Executor to allocate separate threads for handlers
Example of an asynchronous listener
Transaction-bound Application Listeners
Starting with Spring 4.2, the spring provides a new @TransactionalEventListener annotation, which is an extension of @EventListener, that allows binding the listener of an event to a transaction phase.
Available transaction phases:
BEFORE_COMMIT — The listener will handle the event before the transaction is committed.
AFTER_COMMIT — This is the default phase of @TransactionalEventListener. The listener will handle the event when the transaction gets committed successfully.
AFTER_COMPLETION — The listener will handle the event when the transaction is committed or it is rolled back.
AFTER_ROLLBACK — The event will be handled after the transaction had been rolled back.
The following example defines a listener that listens to TransactionalPostCreatedEvent. The event is raised when the starter transaction is being committed.
The listener is bound to the AFTER_COMMIT phase, and it requires a new transaction in order to persist a new PostLog because the previous transaction is about to be committed, thus making it impossible to be reused.
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. If no transaction is running the event isn’t published at all unless we override this by setting fallbackExecution attribute to true.
The following example binds an event to AFTER_ROLLBACK phase
Git Repository
You can access the full project at GitHub here:
https://github.com/semotpan/spring-application-events
Thanks for reading!