Spring Boot - Custom Events
In this lesson, we’ll look at how to use events in Spring. We will also discover how to make custom events in Spring Boot with publisher and subscriber patterns.
Introduction
Events are one of the more overlooked functionalities in the framework but also one of the most useful. And—like many other things in Spring—event publishing is one of the capabilities provided by ApplicationContext.
There are a few simple guidelines to follow:
- the event should extend ApplicationEvent
- the publisher should inject an ApplicationEventPublisher object
- the listener should implement the ApplicationListener interface
Application events are available since the very beginning of the Spring framework as a mean for loosely coupled components to exchange information.
A Custom Event
Spring allows you to create and publish custom events which—by default—are synchronous. This has a few advantages, for example the listener being able to participate in the publisher’s transaction context.
A Simple Application Event
Let’s make a simple event class—just a placeholder to store the event data. In this sample, the event class holds a String message:
public class DiscoverSpringEvent extends ApplicationEvent {
private String message;
public DiscoverSpringEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
A Publisher
Now let’s create a publisher of that event. The publisher constructs the event object and publishes it to anyone who’s listening.
To publish the events, the publisher can simply inject the ApplicationEventPublisher and use the publishEvent() API:
public class DiscoverSpringEventPublisher {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void doStuffAndPublishAnEvent(final String message) {
System.out.println("Publishing custom event. ");
DiscoverSpringEvent discoverSpringEvent = new DiscoverSpringEvent(this, message);
applicationEventPublisher.publishEvent(discoverSpringEvent);
}
}
Alternatively, the publisher class can implement the ApplicationEventPublisherAware interface; this will also inject the event publisher on the application start-up. Usually, it’s simpler to just inject the publisher with @Autowire.
A Listener
Finally, let’s create the listener.
The only requirement for the listener is to be a bean and implement ApplicationListener interface:
@Component
public class DiscoverSpringEventListener implements ApplicationListener<DiscoverSpringEvent> {
@Override
public void onApplicationEvent(DiscoverSpringEvent event) {
System.out.println("Received spring custom event - " + event.getMessage());
}
}
Notice how our custom listener is parametrized with the generic type of discover custom event which makes the onApplicationEvent() method type safe. This also avoids having to check if the object is an instance of a specific event class and casting it.
And, as already discussed, by default spring events are synchronous—the doStuffAndPublishAnEvent() method blocks until all listeners finish processing the event.
Creating Asynchronous Events
In some cases, publishing events synchronously isn’t really what we’re looking for—we may need async handling of our events.
You can turn that on in the configuration by creating a ApplicationEventMulticaster bean with an executor; for our purposes here SimpleAsyncTaskExecutor works well:
@Configuration
public class AsynchronousSpringEventsConfig {
@Bean(name = "applicationEventMulticaster")
public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
SimpleApplicationEventMulticaster eventMulticaster
= new SimpleApplicationEventMulticaster();
eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
return eventMulticaster;
}
}
The event, the publisher, and the listener implementations remain the same as before, but now the listener will asynchronously deal with the even in a separate thread.
Existing Framework Events
Spring itself publishes a variety of events out of the box. For example, the ApplicationContext will fire various framework events e.g. ContextRefreshedEvent, ContextStartedEvent, RequestHandledEvent etc.
These events provide application developers an option to hook into the lifecycle of the application and the context and add in their own custom logic where needed.
Here’s a quick example of a listener listening for context refreshes:
public class ContextRefreshedListener
implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent cse) {
System.out.println("Handling context re-freshed event. ");
}
}
Transaction bound events
Another popular improvement is the ability to bind the listener of an event to a phase of the transaction. The typical example is to handle the event when the transaction has completed successfully: this allows events to be used with more flexibility when the outcome of the current transaction actually matters to the listener.
Spring Framework is currently structured in such a way that the context is not aware of the transaction support and we obviously didn’t want to deviate from that very sane principle so we built an open infrastructure to allow additional components to be registered and influence the way event listeners are created.
The transaction module implements an EventListenerFactory that looks for the new @TransactionalEventListener annotation. When this one is present, an extended event listener that is aware of the transaction is registered instead of the default.
Let’s reuse our example above and rewrite it in such a way that the order creation event will only be processed when the transaction in which the producer is running has completed successfully:
@Component
public class MyComponent {
@TransactionalEventListener(condition = "#creationEvent.awesome")
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
Not much to see, right? @TransactionalEventListener is a regular @EventListener and also exposes a TransactionPhase, the default being AFTER_COMMIT. You can also hook other phases of the transaction (BEFORE_COMMIT, AFTER_ROLLBACK and AFTER_COMPLETION that is just an alias for AFTER_COMMIT and AFTER_ROLLBACK).
By default, if no transaction is running the event isn’t sent at all as we can’t obviously honor the requested phase, but there is a fallbackExecution attribute in @TransactionalEventListener that tells Spring to invoke the listener immediately if there is no transaction.
Generics support
It is now possible to define your ApplicationListener implementation with nested generics information in the event type, something like:
public class MyListener
implements ApplicationListener<MyEvent<Order>> { ... }
When dispatching an event, the signature of your listener is used to determine if it matches said incoming event.
Due to type erasure you need to publish an event that resolves the generics parameter you would filter on, something like MyOrderEvent extends MyEvent<Order>. There might be other workarounds and we are happy to revisit the signature matching algorithm if the community thinks it worthwhile.
Conclusion
In this quick tutorial we went over the basics of dealing with events in Spring – creating a simple, custom event, pushing it and then handling it in a listener. We also had a brief look at how to enable asynchronous processing of events in the configuration.
Recent Stories
Top DiscoverSDK Experts
Compare Products
Select up to three two products to compare by clicking on the compare icon () of each product.
{{compareToolModel.Error}}
{{CommentsModel.TotalCount}} Comments
Your Comment