Events in Spring
In this article, we’re going to discuss how to use events in Spring and make a practical example of this feature.
1. Introduction
When we think about routines in our applications, we can take some extra tools for queueing like RabbitMQ, Apache Kafka, ActiveMQ.
Spring has a powerful tool provided by ApplicationContext
called Event. Basically, an event is a message published by some routine, regarding know for who and save any response.
Imagine an application that needs to log a creating user process and send an e-mail. We can use the event feature in Spring without any other structure to do that.
The next steps show how to create a custom event with a synchronous approach. Furthermore, we’ll see an asynchronous approach and take a look at Annotation-Driven listeners.
2. Pre-requisites
The minimum Java version for executing the article’s example is JDK 8 (find here), but we can use the most recently released Java version (JDK 15).
Also, I’m using IntelliJ 2020.2, but you can use any IDE with support for the versions recommended above.
3. How to use events in Spring
To start working with events in Spring, we’ll need three important things:
- An Event class to create the event
- A Publisher class to publish our event
- A Listener class to listen to the event from the publisher
- A Processor to deal with the above classes
The events in Spring are synchronous by default. For the next steps, we’re going to create a service to generate Tickets and PIN and will log them into our application.
3.1 Event class
First, to create our event class we’ll need to extend ApplicationEvent
class.
TicketEvent class
public class TicketEvent extends ApplicationEvent { private TicketEventPayload payload; public TicketEvent(Object source, TicketEventPayload payload) { super(source); this.payload = payload; } public TicketEventPayload getPayload() { return this.payload; }
As we notice, the class constructor will have a payload and an object to be used by the processor to start our event.
The TicketPayload
class is the service’s payload that is used in the process. It’s a simple POJO to transport our message through the event.
TicketPayload class
public class TicketPayload { private String id; private String pin; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPin() { return pin; } public void setPin(String pin) { this.pin = pin; }
3.2 Publisher class
Next, we’ll create our publisher that sends the event. The publisher must implement the interface ApplicationEventPublisherAware
.
Listener class
@Component public class TicketEventPublisher implements ApplicationEventPublisherAware { private ApplicationEventPublisher applicationEventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } public void publish (TicketEvent event) { this.applicationEventPublisher.publishEvent(event); } }
The publisher will take our event and publish to all listeners that our applications has.
3.3 Listener class
Moving forward, the listener class is responsible to register the log of ticket generation. We can have more listeners to do a lot of other routines in our application, just needed to “listen” the TicketEvent
class from the publisher.
Publisher class
@Component public class TicketEventListener implements ApplicationListener { @Override public void onApplicationEvent(TicketEvent event) { logEvent(event.getPayload()); } private void logEvent(TicketPayload payload) { System.out.println(String.format("Ticket %s generated with pin %s at %s", payload.getId(), payload.getPin(), new Date())); }
In the above class, we see the onApplicationEvent()
the method that takes the event TicketEvent
payload and calls the logEvent()
method.
3.4 Processor
Finally, we have a processor class that will work with the event, publisher and listener classes.
Processor class
@Component public class TicketEventProcessor { @Autowired private TicketEventPublisher publisher; public TicketPayload process() { TicketPayload payload = new TicketPayload(); payload.setId(UUID.randomUUID().toString()); payload.setPin(StringUtils.leftPad(String.valueOf(new Random().nextInt(9999)),4, "0")); TicketEvent event = new TicketEvent(this,payload); publisher.publish(event); return payload; } }
Basically, our processor creates the payload and put in the event. The publisher is called to publish the event.
4. Testing our application
To test our application, we can use an API testing tool. I recommend Postman to do the test on our application, but you can use any other tool of your knowledge.
Also, you can use a browser (Chrome, Firefox, Edge) as our application just has one GET method :-)
To test the application, I’ll use Postman. So, a simple GET with this URL is enough to do the test: http://localhost:8088/api/v1/ticket/generate.
The result is showed as below:
Application test
Ticket bafc858e-4da1-4814-8bc2-2f46026022fa generated with pin 5103!
Now, let’s see how it works in the application log:
Application log
Start: Mon Feb 22 21:30:03 WET 2021 Ticket bafc858e-4da1-4814-8bc2-2f46026022fa generated with pin 5103 at Mon Feb 22 21:30:03 WET 2021 Finish: Mon Feb 22 21:30:03 WET 2021
As we see, the listener takes the event and register into the log the ticket ID and the PIN through the payload.
5. Asynchronous event
Spring events are by default synchronous as said before. Now, let’s make some changes on our application switching to an asynchronous approach.
First, we’ll create a configuration class by using the ApplicationEventMulticaster
bean. Also, we’ll need an executor that, in this case, is the SimpleApplicationEventMulticaster
class to make our life easier.
Asynchronous configuration class
@Configuration public class AsynchronousTicketEventsConfig { @Bean(name = "applicationEventMulticaster") public ApplicationEventMulticaster simpleApplicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); return eventMulticaster; } }
The event, the publisher, and the listener remain the same as before. But now, the listener will asynchronously deal with the published events in a separated thread.
Basically, the SimpleApplicationEventMulticaster
class doesn’t permit multiple instances of the same listener, as it keeps listeners in a HashSet, avoiding a listener to block the entire application.
5.1 Testing asynchronous
To test this asynchronous service, let’s use Postman again but with a different URL: http://localhost:8089/api/v1/ticket/generate.
The result will be the same, but if we check the log, we’ll see some difference with the previous synchronous event:
Asynchronous event log
Start: Mon Feb 22 21:32:01 WET 2021 Finish: Mon Feb 22 21:32:01 WET 2021 Ticket 1f9b489d-31b8-4bd8-b56e-5b29d1e36dc8 generated with pin 4503 at Mon Feb 22 21:32:01 WET 2021
We notice that the process starts and finish before the ticket and PIN generation. Maybe this not so much different from synchronous because it’s a small application, but imagine a bigger application with thousands of requests and routines.
6. Annotation-Driven Event Listener
Spring also gives support to annotation-driven to implement listeners. Starting with Spring 4.2, the annotation @EventListener
can be used instead of implementing ApplicationListener
interface.
Annotation-Driven example
@Component public class TicketEventListener { @EventListener public void onApplicationEvent(TicketEvent event) { logEvent(event.getPayload()); } private void logEvent(TicketPayload payload) { System.out.println(String.format("Ticket %s generated with pin %s at %s", payload.getId(), payload.getPin(), new Date())); } }
Above, we just changed our class by removing the ApplicationListener
implementing and added the @EventListener
annotation.
The rest of our application (publisher, event, processor) remains the same. To test this feature, use the following URL: http://localhost:8090/api/v1/ticket/generate.
7. Generics Support
We can also use Generics support for events in Spring.
First, let’s create a generic class to structure our events:
Generic class
public class GenericEvent { private T object; public GenericEvent(T object) { this.object = object; } public T getObject() { return object; } }
Now, we can create any kind of event using this class, and isn’t required to extends ApplicationEvent
anymore.
7.1 TicketEvent to Generic support
Second, we change our TicketEvent to inherit our GenericEvent class to allow the TicketEvent class to work with generic:
TicketEvent change
public class TicketEvent extends GenericEvent { public TicketEvent(TicketPayload payload) { super(payload); } }
Here we put our TicketPayload
to use in the event of ticket generating.
7.2 Listener changes
Next, we change our listener class to adapt to our generic approach:
Listener using generic class
@Component public class TicketEventListener { @EventListener public void onApplicationEvent(GenericEvent event) { logEvent(event.getObject()); } private void logEvent(TicketPayload payload) { System.out.println(String.format("Ticket %s generated with pin %s at %s", payload.getId(), payload.getPin(), new Date())); } }
Note that we are using the @EventListener
annotation as we saw before. That’s because if we use the other way, we’ll need to inherit the ApplicationEvent
class on the GenericEvent
class.
The publisher and the processor remain the same. Finally, to test this new feature, use the URL http://localhost:8091/api/v1/ticket/generate.
8. Spring Framework Events
Spring has some built-in eventing mechanisms that we can use in our application. We can use them by listening and do some customized processes.
Here, an example of ContextStartedEvent
use through ConfigurableApplicationContext
interface.
Listener with framework event
@Component public class TicketEventListener { @EventListener public void handleContextStart(final ContextStartedEvent cse) { System.out.println("Start event: " + new Date()); } @EventListener public void onApplicationEvent(TicketEvent event) { logEvent(event.getPayload()); } private void logEvent(TicketPayload payload) { System.out.println(String.format("Ticket %s generated with pin %s at %s", payload.getId(), payload.getPin(), new Date())); }
In our TicketEventListener
, we add a listener to deal with ContextStartedEvent
. Also, we change our processor to trigger this listener with the command start()
embedded in ContextStartedEvent
class.
Processor triggering framework event
@Component public class TicketEventProcessor { @Autowired private TicketEventPublisher publisher; @Autowired private ConfigurableApplicationContext ctx; public TicketPayload process() { TicketPayload payload = new TicketPayload(); payload.setId(UUID.randomUUID().toString()); payload.setPin(StringUtils.leftPad(String.valueOf(new Random().nextInt(9999)),4, "0")); TicketEvent event = new TicketEvent(this,payload); ctx.start(); publisher.publish(event); return payload; } }
Note that now we have a new autowired variable ConfigurableApplicationContext
. This class needs to be initialized in our application main class as following:
Main class
@SpringBootApplication public class SpringeventsApplication { public static void main(String[] args) { SpringApplication.run(SpringeventsApplication.class, args); ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(EventConfiguration.class); } }
Finally, to test we use the URL http://localhost:8090/api/v1/ticket/generate. The log will register the event start using the ContextStartedEvent.
Ticket generation
Start event: Sun Feb 28 17:00:02 WET 2021 Ticket 6a1907ae-5aae-4c01-9c7b-e2146935e9cb generated with pin 9381 at Sun Feb 28 17:00:02 WET 2021
Spring has other standard events like ContextRefreshedEvent, RequestHandledEvent that you can explore and use in the application too.
9. Conclusion
In conclusion, we see how to use events in Spring creating the publisher and listener classes handled by a processor. Also, we see that events are synchronous by default and make our asynchronous event with some changes.
Further, we discuss how to work with annotation-driven and added generic support to our event application and finishing with the frameworks events that can be used on the application.
10. Download the source code
You can download the full source code of this example here: Events in Spring