Annotation-driven event listeners in Spring 4.2
Introduction
Exchanging events within the application has become indispensable part of many applications and thankfully Spring provides a complete infrastructure for transient events (*). The recent refactoring of transaction bound events gave me an excuse to check in practice the new annotation-driven event listeners introduced in Spring 4.2. Let’s see what can be gained.
(*) – for persistent events in Spring-based application Duramen could be a solution that is worth to see
The old way
To get a notification about an event (both Spring event and custom domain event) a component implementing ApplicationListener
with onApplicationEvent
has to be created.
@Component class OldWayBlogModifiedEventListener implements ApplicationListener<OldWayBlogModifiedEvent> { (...) @Override public void onApplicationEvent(OldWayBlogModifiedEvent event) { externalNotificationSender.oldWayBlogModified(event); } }
It works fine, but for every event a new class has to be created which generates boilerplate code.
In addition our event has to extend ApplicationEvent
class – the base class for all application events in Spring.
class OldWayBlogModifiedEvent extends ApplicationEvent { public OldWayBlogModifiedEvent(Blog blog) { super(blog); } public Blog getBlog() { return (Blog)getSource(); } }
Please notice that using domain objects in the events has notable drawback and is not the best idea in many situations. Pseudodomain objects in the code examples were used to not introduce unnecessary complexity.
Btw, ExternalNotificationSender
in this example is an instance of a class which sends external notifications to registered users (e.g. via email, SMS or Slack).
Annotation-driven event listener
Starting with Spring 4.2 to be notified about the new event it is enough to annotate a method in any Spring component with @EventListener
annotation.
@EventListener public void blogModified(BlogModifiedEvent blogModifiedEvent) { externalNotificationSender.blogModified(blogModifiedEvent); }
Under the hood Spring will create an ApplicationListener
instance for the event with a type taken from the method argument. There is no limitation on the number of annotated methods in one class – all related event handlers can be grouped into one class.
Conditional event handling
To make @EventListener
even more interesting there is an ability to handle only those events of a given type which fulfill given condition(s) written in SpEL. Let’s assume the following event class:
public class BlogModifiedEvent { private final Blog blog; private final boolean importantChange; public BlogModifiedEvent(Blog blog) { this(blog, false); } public BlogModifiedEvent(Blog blog, boolean importantChange) { this.blog = blog; this.importantChange = importantChange; } public Blog getBlog() { return blog; } public boolean isImportantChange() { return importantChange; } }
Please note that in the real application there would be probably a hierarchy of Blog related events.
Please also note that in Groovy that class would be much simpler.
To generate event only for important changes the condition
parameter can be used:
@EventListener(condition = "#blogModifiedEvent.importantChange") public void blogModifiedSpEL(BlogModifiedEvent blogModifiedEvent) { externalNotificationSender.blogModifiedSpEL(blogModifiedEvent); }
Relaxed event type hierarchy
Historically ApplicationEventPublisher
had only an ability to publish objects which inherited after ApplicationEvent. Starting with Spring 4.2 the interface has been extended to support any object type. In that case the object is wrapped in PayloadApplicationEvent
and sent through.
//base class with Blog field - no need to extend `ApplicationEvent` class BaseBlogEvent {} class BlogModifiedEvent extends BaseBlogEvent {}
//somewhere in the code ApplicationEventPublisher publisher = (...); //injected publisher.publishEvent(new BlogModifiedEvent(blog)); //just plain instance of the event
That change makes publishing events even easier. However, on the other hand without an internal conscientiousness (e.g. with marker interface for all our domain events) it can make event tracking even harder, especially in larger applications.
Publishing events in response to
Another nice thing with @EventListener
is the fact that in a situation of non-void return type Spring will automatically publish returned event.
@EventListener public BlogModifiedResponseEvent blogModifiedWithResponse(BlogModifiedEvent blogModifiedEvent) { externalNotificationSender.blogModifiedWithResponse(blogModifiedEvent); return new BlogModifiedResponseEvent( blogModifiedEvent.getBlog(), BlogModifiedResponseEvent.Status.OK); }
Asynchronous event processing
Updated. As rightly suggested by Radek Grębski it is also worth to mention that @EventListener
can be easily combined with @Async
annotation to provide asynchronous event processing. The code in the particular event listener doesn’t block neither the main code execution nor processing by other listeners.
@Async //Remember to enable asynchronous method execution //in your application with @EnableAsync @EventListener public void blogAddedAsync(BlogAddedEvent blogAddedEvent) { externalNotificationSender.blogAdded(blogAddedEvent); }
To make it work it is only required to enable asynchronous method execution in general in your Spring context/application with @EnableAsync
.
Summary
Annotation-driven event listeners introduced in Spring 4.2 continue a trend to reduce boilerplate code in Spring (Boot) based applications. The new approach looks interesting especially for small applications with a small amount of events where a maintenance overhead is lower. In the world of ubiquitous Spring (Boot) magic it is more worthy to remember that with great power comes great responsibility.
In the next blog post I will write how the new mechanism can be also used to simplify handling of transaction bound events.
Please note that Spring Framework 4.2 is a default dependency of Spring Boot 1.3 (at the time of writing 1.3.0.M5 is available). Alternatively it is possible to manually upgrade Spring Framework version in Gradle/Maven for Spring Boot 1.2.5 – it should work for most of the cases.
Reference: | Annotation-driven event listeners in Spring 4.2 from our JCG partner Marcin Zajaczkowski at the Solid Soft blog. |
Hi, Great tutorial.
I have a question, would it be possible to implement this across two applications? One publishing the event and the other subscribing/consuming?
Regards
Zayden