Published in ·
--
What exactly is Spring Event? When to use it? How to use it well?
The spring event mechanism is a feature of the Spring Framework that allows components to communicate with each other in a loosely coupled manner. It is based on the Observer design pattern, where an object [the subject] maintains a list of its dependents [the observers] and notifies them automatically of any state changes. In Spring, an event is represented by an instance of the ApplicationEvent class or one of its subclasses.
The core classes of the observer pattern in Spring are as follows:
- ApplicationListener: An application listener, or observer, that extends EventListener from JDK. When the event being listened to occurs, the only method in this class, onApplication() will be called.
- ApplicationEvent: An abstract event class that extends EventObject from JDK. All events in Spring and SpringBoot are subclasses of this class.
- ApplicationEventMulticaster: The event registration and broadcasting center, used for registering listeners and broadcasting events.
- ApplicationContext: The IOC container in Spring, which is also a publisher because ApplicationContext extends ApplicationEventPublisher and can publish events through the publishEvent(Object event) method.
These classes together form the core of the observer pattern in Spring, allowing for flexible event-driven programming within the framework.
Under the Spring Event mechanism, components in a Spring application can communicate with each other without knowing about each other directly. This allows for a more flexible and loosely coupled architecture, where components can be added, removed, or replaced easily without affecting other components in the system.
The below diagram shows the event publish workflow in Spring source code.
ok. let’s take a look at the core class of the spring event:
public abstract class ApplicationEvent extends EventObject { /** use serialVersionUID from Spring 1.2 for interoperability. */
private static final long serialVersionUID = 7099057708183571937L;
/** System time when the event happened. */
private final long timestamp;
/**
* Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
* set to {@link System#currentTimeMillis()}.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
* @see #ApplicationEvent(Object, Clock)
*/
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
/**
* Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
* set to the value returned by {@link Clock#millis()} in the provided {@link Clock}.
* <p>This constructor is typically used in testing scenarios.
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
* @param clock a clock which will provide the timestamp
* @since 5.3.8
* @see #ApplicationEvent(Object)
*/
public ApplicationEvent(Object source, Clock clock) {
super(source);
this.timestamp = clock.millis();
}
/**
* Return the time in milliseconds when the event occurred.
* @see #ApplicationEvent(Object)
* @see #ApplicationEvent(Object, Clock)
*/
public final long getTimestamp() {
return this.timestamp;
}
}
ApplicationEvent is an abstract class in the Spring Framework that serves as a base class for all application-level events in Spring. It provides a consistent API for publishing and consuming events in the Spring Framework.
the ApplicationEvent class provides several constructors that allow subclasses to initialize the event with the necessary information. This includes the source of the event, which is typically the object that published the event, as well as any additional data that the event may need to carry.
By extending the ApplicationEvent class, developers can create their own custom events and publish them using the Spring event mechanism. This allows for a flexible and extensible architecture in which different components can communicate with each other through events, rather than through direct method calls or other forms of coupling.
The main application scenarios for each ApplicationEvent in the above diagram are as follows:
- ServletRequestHandledEvent: This event is published by the Spring Web MVC framework after a request has been handled by a @RequestMapping method, similar to RequestHandledEvent. However, unlike RequestHandledEvent, ServletRequestHandledEvent provides more detailed information about the request and response objects.
- PayloadApplicationEvent:spring-context module classes, early Spring only supports publishing ApplicationEvent type events, since Spring 4.2 can publish non-ApplicationEvent type events, when the event parameters will be wrapped to PayloadApplicationEvent, and then published. and then published.
- ApplicationContextEvent: this is a type of event in Spring Framework that is published by the application context when its state changes. This event is used to track the lifecycle of the application context and to perform certain actions when the context is initialized, refreshed, or closed.
- ContextStartedEvent: This event is published when the application context is started, typically when the start() method is called on the context.
- ContextStoppedEvent: This event is published when the application context is stopped, typically when the stop() method is called on the context.
- ContextRefreshedEvent: This event is published when the application context is initialized or refreshed. This typically happens when the application starts up, or when the context is manually refreshed.
- ContextClosedEvent: This event is published when the application context is closed, typically when the close() method is called on the context.
By listening to these events, developers can perform certain actions at different stages of the application context lifecycle. For example, they can initialize beans when the context is refreshed, start background threads when the context is started, or release resources when the context is closed.
Spring Boot builds on top of the Spring Framework and provides additional features and conventions that make it easier to create production-ready applications. One of the ways that Spring Boot takes advantage of Spring events is by providing pre-built event listeners that can be used out of the box.
For example, Spring Boot provides a SpringApplication class that is used to bootstrap the application context and start the application. This class also publishes several events during the application startup and shutdown process, including:
- ApplicationStartingEvent: This event is published at the very beginning of the application startup process before the application context is created.
- ApplicationEnvironmentPreparedEvent: This event is published after the application context is created, but before the environment is prepared.
- ApplicationPreparedEvent: This event is published after the environment is prepared, but before the application context is refreshed.
- ApplicationStartedEvent: This event is published when the application context is refreshed and the application has started.
- ApplicationReadyEvent: This event is published after the application has started and is ready to service requests.
- ApplicationFailedEvent: This event is published if the application fails to start.
When a Spring Boot application starts up, it uses a class called EventPublishingRunListener to publish events to Spring’s event mechanism. This class is a Spring Boot-specific implementation of the ApplicationListener interface that listens for specific application events and publishes them to Spring’s event mechanism.
When EventPublishingRunListener receives a start state event from SpringApplication, it broadcasts the event to all registered Spring listeners using a SimpleApplicationEventMulticaster. This is the same mechanism used by Spring’s event model, and it allows Spring Boot to take advantage of the existing infrastructure provided by Spring to manage events.
So, in summary, Spring Boot’s event mechanism makes use of Spring’s event model by using a Spring Boot-specific implementation of the ApplicationListener interface to publish events to the existing Spring event infrastructure.
ok. let’s take a look at the core source code:
class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
multicastInitialEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
multicastInitialEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
multicastInitialEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware contextAware) {
contextAware.setApplicationContext(context);
}
context.addApplicationListener(listener);
}
multicastInitialEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all the context's listeners instead
if (context instanceof AbstractApplicationContext abstractApplicationContext) {
for (ApplicationListener<?> listener : abstractApplicationContext.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private void multicastInitialEvent(ApplicationEvent event) {
refreshApplicationListeners();
this.initialMulticaster.multicastEvent(event);
}
private void refreshApplicationListeners() {
this.application.getListeners().forEach(this.initialMulticaster::addApplicationListener);
}
private static class LoggingErrorHandler implements ErrorHandler {
private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
As you can see, EventPublishingRunListener implements the SpringApplicationRunListener interface, which defines a set of methods that are called during the Spring Boot application startup process.
When EventPublishingRunListener is constructed, it creates a new SimpleApplicationEventMulticaster and adds all of the application listeners that were registered with the SpringApplication instance.
Each of the methods in EventPublishingRunListener corresponds to a specific event in the Spring Boot startup process.
For example:
- the started() method is called when the application context has been started
- the running() method is called when the application is ready to receive requests.
In each of these methods, EventPublishingRunListener publishes the appropriate event to the Spring Framework’s event infrastructure using the ApplicationEventMulticaster instance that was created during construction.
Overall, EventPublishingRunListener is a critical component of the Spring Boot startup process, as it allows events to be published to the Spring Framework’s event mechanism, enabling other components and applications to listen for and react to these events.
Then what can we get from the spring event mechanism?
You should use the Spring event mechanism when you want to decouple components in your application and allow them to communicate with each other without knowing about each other directly. This can make your application more flexible and easier to maintain. You can use events to trigger actions in response to state changes, such as updating a cache or sending a notification. You can also use events to propagate exceptions or other error conditions across your application.
below I listed many scenarios maybe you will interest
- Triggering actions based on certain events: you could listen for a UserLoggedInEvent and perform some action such as logging the event, updating a user’s last login timestamp, or sending a welcome message to the user.
- Decoupling components: if you have a component that generates notifications, you could define an event that is fired when a new notification is generated, and have other components listen for this event to perform additional actions.
- Building extensible systems: you could define a set of events that can be triggered when certain actions occur in your application, and allow developers to register their own listeners to perform additional actions.
- Auditing and logging: you could define an event that is fired whenever a database record is updated, and have a listener that logs the change and the user who made the change.
- Cache invalidation: you could define an event that is fired when a user’s profile is updated, and have a listener that invalidates the cached version of the profile so that the updated version is retrieved from the database the next time it’s requested.
- Integration with external systems: you could define an event that is fired when a new order is placed in your application, and have a listener that sends the order information to an external system via a message queue or API.
- Implementing business rules: you could define an event that is fired when a new order is placed, and have a listener that checks the order against a set of business rules to determine whether it can be processed.
- Real-time updates: you could define an event that is fired when a new comment is posted on a blog post, and have a listener that updates the page in real-time with the new comment.
- Error handling: you could define an event that is fired when an error occurs in your application, and have a listener that logs the error and sends an email to the support team.
- Feature toggles: you could define an event that is fired when a new feature is released, and have a listener that enables the feature for a certain subset of users.
- User notifications: you could define an event that is fired when a user’s subscription is about to expire, and have a listener that sends a notification to the user reminding them to renew their subscription.
- Workflow automation: you could define an event that is fired when a new order is received, and have a listener that automatically assigns the order to the appropriate team for processing.
ok. After understanding what and when. let's go to how to use it.
- Create a custom event extends from ApplicationEvent
- Define event listeners implementing ApplicationListener
- Publish events in the Spring container
Code
- Create your own Event
public class CustomizeEvent extends ApplicationEvent { private String msg;
public CustomizeEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
2. Define a listener
there are 2 recommended ways:
- Annotation
- Programmatic
// Annotation@Component
@Slf4j
public class AnnotationCustomizeListener {
@EventListener
public void eventListener(CustomizeEvent event){
log.info("Annotation Customized Listener catch the Event and the message: {}", event.getMsg());
}
}
// Programmatic
@Component
@Slf4j
public class CustomizeListener implements ApplicationListener<CustomizeEvent> {
@Override
public void onApplicationEvent(CustomizeEvent event) {
log.info("CustomizeListener catch the Event and the message: {}", event.getMsg());
}
}
3. Publish the event by:
- ApplicationContext (which extends from ApplicationEventPublisher so it still uses the same publishEvent() method)
- ApplicationEventPublisher
//publish when start the application.@Component
public class CustomizePublisher {
@Autowired
ApplicationContext applicationContext;
public void publish(String msg) {
applicationContext.publishEvent(new CustomizeEvent(this, msg));
}
}
//publish when some api been called.
@RestController
@RequestMapping("/event")
public class EventController {
@Autowired private ApplicationEventPublisher publisher;
@Autowired private ApplicationContext applicationContext;
@RequestMapping("/t1")
public void sendEvent1() {
applicationContext.publishEvent(new CustomizeEvent(new Object(), "Hi applicationContext publish Event"));
}
/**
* ApplicationContext extends from ApplicationEventPublisher so it still use same the publishEvent() method
*/
@RequestMapping("/t2")
public void sendEvent2() {
publisher.publishEvent(new CustomizeEvent(new Object(), "Hi ApplicationEventPublisher publish Event"));
}
}
Advanced
4. Ordering Listeners:
In some scenarios, it may be necessary to ensure that certain event listeners are executed before or after others. Spring provides the @Order annotation to specify the execution order of event listeners.
By default, listeners are executed in the order of registration. To use @Order, simply annotate the event listener methods with @Order and specify a value. The listener with the lowest value will be executed first, and listeners with the same value will be executed in the order of registration.
@Component
@Slf4j
public class AnnotationOrderListener { @EventListener
@Order(2)
public void order2(CustomizeEvent event){
log.info("CustomizeEvent with order {2}");
}
@EventListener
@Order(1)
public void order1(CustomizeEvent event){
log.info("CustomizeEvent with order {1}");
}
}
5. Asynchronous Listeners
Spring also supports asynchronous event listeners. This means that event listeners can be executed in a separate thread, which can improve performance in some scenarios.
To make an event listener asynchronous, simply annotate the listener method with @Async. This will cause the method to be executed in a separate thread managed by Spring’s task executor.
@EnableAsync
@SpringBootApplication
public class SpringEventApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEventApplication.class, args);
}
}@Component
@Slf4j
public class AnnotationAsynchronousListener {
@EventListener
@Async
public void AsyncListener(CustomizeEvent event){
log.info("CustomizeEvent Asynchronous Listener");
}
}
It’s important to be careful when using asynchronous event listeners, as they can introduce complexity and potential issues. For example, if an event listener modifies a shared state, you must ensure that access to the shared state is synchronized or managed correctly to avoid race conditions.
Additionally, if an event listener throws an exception, it may not be caught and handled correctly by the calling thread, which can lead to unexpected behavior.
As with all asynchronous programming, it’s important to understand the potential issues and design your code carefully.
- Keep events decoupled from listeners: Define your events in a separate module or package from your listeners to ensure that changes to one don’t affect the other.
- Use async listeners for long-running tasks: If a listener performs a time-consuming task, consider using an asynchronous listener to prevent the main thread from blocking.
- Use event-driven architecture where appropriate: Events are a great way to implement event-driven architectures, where services can communicate through events instead of direct calls. This can make your system more scalable and flexible.
- Use context events to initialize resources: Context events, such as ContextRefreshedEvent, can be used to initialize resources that are needed by your application, such as database connections or caches.
- Avoid using events for critical functionality: Events should be used for non-critical functionality, such as logging, auditing, or triggering background tasks. Avoid using events for critical functions such as transaction management or security checks.
- Use generics to ensure type safety: When defining custom events, use generics to ensure type safety and prevent class cast exceptions.
- Test your event listeners: Write unit tests to ensure that your event listeners are working as expected and handling events correctly.
By following these best practices, you can ensure that your use of Spring events is effective, efficient, and maintainable.
Overall, the Spring event mechanism is a very powerful feature of the Spring Framework that enables a more flexible and maintainable architecture for Java applications.
Thanks for reading! If you like it or feel it helped pls click Applaud. Thanks :) Happy coding. See you next time.
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 💰 Free coding interview course ⇒ View Course
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job