spring

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:

  1. An Event class to create the event
  2. A Publisher class to publish our event
  3. A Listener class to listen to the event from the publisher
  4. 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

Download
You can download the full source code of this example here: Events in Spring

Sergio Lauriano Junior

Sergio is graduated in Software Development in the University City of São Paulo (UNICID). During his career, he get involved in a large number of projects such as telecommunications, billing, data processing, health and financial services. Currently, he works in financial area using mainly Java and IBM technologies.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button