Introduction:

In this article, you will learn about Custom Events in Spring. Like many capabilities provided by the Spring framework, events are one of the most useful features provided by ApplicationContext in Spring. Spring allows you to publish/subscribe events synchronously as well as asynchronously.

There are several standard Events in Spring as ContextRefreshedEvent, ContextStartedEvent, ContextStoppedEvent, ContextClosedEvent, RequestHandledEvent and ServletRequestHandledEvent. Spring also allows you to create your custom event classes based on your application need. We will first learn to create a custom event and then explore the list of standard ones in another article.

Custom events can be created synchronously as well as asynchronously. The asynchronously way of creation may look complicated but provides better performance due to the non-blocking nature of execution.

A. Synchronous custom Spring Event

There are just few simple rules that needs to be followed to pub/sub events synchronously.

  • The event class should extend ApplicationEvent.
  • The publisher has to make use of ApplicationEventPublisher.
  • The event listener should implement the ApplicationListener.

1. Create the Custom event class

The custom event class has to extend the ApplicationEvent abstract class. We will create a custom UserEvent class as below.

UserEvent.java
package com.jsbd.events;
import org.springframework.context.ApplicationEvent;
import java.util.StringJoiner;

public class UserEvent extends ApplicationEvent {

  //Custom property
  private String message;

  //Custom property
  private Integer eventId;

  public UserEvent(Object source) {
    super(source);
  }

  public UserEvent(Object source, String message,
                   Integer eventId) {
    super(source);
    this.message = message;
    this.eventId = eventId;
  }

  public String getMessage() {
    return this.message;
  }

  public Integer getEventId() {
    return this.eventId;
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", UserEvent.class.getSimpleName() + "[", "]")
        .add("message='" + message + "'")
        .add("source=" + source)
        .add("eventId=" + eventId)
        .toString();
  }
}

2. Create an event Listener class

We will create an EventListener by implementing the ApplicationListener interface and its method onApplicationEvent. As you can see this method accepts UserEvent as a parameter. Whenever there is an userEvent published, this method will get executed.

EventListener.java
package com.jsbd.events;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class EventListener implements ApplicationListener<UserEvent> {

  @Override
  public void onApplicationEvent(UserEvent event) {
    System.out.println("======= UserEvent Listener =======");
    System.out.println(event);
  }
}

3. Create an event Publisher class

The Spring framework already provides us ApplicationEventPublisher, we will autowire this using @Autowired inside our custom event publisher class.

EventPublisher.java
@Component
public class EventPublisher {

  @Autowired //Use setter based injection in real code
  private ApplicationEventPublisher applicationEventPublisher;

  public void publishEvent(String message, Integer eventId) {
    applicationEventPublisher.publishEvent(
        new UserEvent(this, message, eventId)
    );
  }
}

4. Publish an event and test the example

Now, we will write a simple JUnit test case to publish an event and check if the listener receives it. I have also added the configuration needed to run this example.

AppConfig
@Configuration
@ComponentScan("com.jsbd.events")
public class AppConfig {

}
Junit test
@SpringJUnitConfig(AppConfig.class)
class EventPublisherTest {

  @Autowired
  EventPublisher eventPublisher;
  
  @Test
  public void sendSynchronousEvent() {
    eventPublisher.publishEvent("User registered", 101);
  }

Output:

======= UserEvent Listener =======
UserEvent[message='User registered', source=com.jsbd.events.EventPublisher@abf688e, eventId=101]

5. Use @EventListener to subscribe events

Spring 4.2 onwards, it is possible to just annotate a method with @EventListener annotation and have the custom event as the method parameter. Spring will mark the method as a listener for the specified event UserEvent.

AnnEventListener
@Component
public class AnnEventListener {

  @EventListener
   public void genericListener(UserEvent userEvent) {
      System.out.println("\n===== General UserEvent Listener =====");
      System.out.println(userEvent);
  }
}

To test this method, we can just send an event using our EventPublisher instance as shown before. The listener will work without any extra config.

B. Asynchronous event listener using @async

Synchronous publish-subscribe is blocking in nature, can have bad impact on application performance. To get rid of this, Spring provides asynchronous event listener using @async annotation.

The first step is to enable the async processing by annotating the configuration with @EnableAsync as below.

AppConfig
@Configuration
@ComponentScan("com.jsbd.events")
@EnableAsync
public class AppConfig {
  //other configs if needed
}

Then, the second step is to use @async with the listener as shown below

AnnEventListener
@Component
public class AnnEventListener {

 //Async Event listener
  @EventListener
  @Async
  public void asyncListener(UserEvent userEvent) {
    System.out.println("===== Async UserEvent Listener =====");
    System.out.println(userEvent);
  }
}

Optionally, if you want asynchronous message processing to be enabled globally for ApplicationContext, use the following configurations. You will not need to use @async annotation.

ApplicationEventMulticaster
  @Bean
  @Scope("singleton")
  ApplicationEventMulticaster eventMulticaster() {
  
    SimpleApplicationEventMulticaster eventMulticaster 
                                            = new SimpleApplicationEventMulticaster();
    eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
    //optionally set the ErrorHandler
    eventMulticaster.setErrorHandler(TaskUtils.LOG_AND_PROPAGATE_ERROR_HANDLER);
    return eventMulticaster;
  }

C. Runtime event Filtering using SpEL

Occasionally, you may want to filter out the events delivered to the listener. Spring allows you to define a SpEL based expression based on which the matched values are delivered to the event listener. Only the UserEvents with the eventId ==120 are delivered to this listener.

AnnEventListener
@Component
public class AnnEventListener {

  //Event Listener with a Spring SpEL expression
  @EventListener(condition = "#userEvent.eventId == 102")
  public void processEvent(UserEvent userEvent) {
    System.out.println("====== Conditional UserEvent Listener =====");
    System.out.println(userEvent);
  }
}

D. Ordering EventListeners using @Order

You can use the @Order annotation when you want a specific eventListener to get invoked before another one. Remember, the annotation with the least value has the highest execution priority. So in the below example, listenerA gets executed before listenerB.

AnnEventListener
@Component
public class AnnEventListener {

  @EventListener
  @Order(1)
  public void listenerA(UserEvent userEvent) {
    System.out.println("\n===== UserEvent ListenerA =====");
    System.out.println(userEvent);
  }

  @EventListener
  @Order(5)
  public void listenerB(UserEvent userEvent) {
    System.out.println("\n===== UserEvent ListenerB =====");
    System.out.println(userEvent);
  }
}

E. Generic events

Spring also allows you to use a generic event class to publish any type of event. As shown in the below code, instead of creating too many classes for each event types, you can just create a GenericEvent class with more properties to send any type of event.

GenericEvent
public class GenericEvent<T> implements ResolvableTypeProvider {
  private T message;
  private Object source;
  
  public GenericEvent(Object source, T message) {
    this.source = source;
    this.message = message;
  }
  
  @Override
  public ResolvableType getResolvableType() {
    return ResolvableType.forClassWithGenerics(
        getClass(), ResolvableType.forInstance(getSource())
    );
  }
  
  public T getMessage() {
    return message;
  }
  
  public Object getSource() {
    return source;
  }
  
  @Override
  public String toString() {
    return new StringJoiner(", ", GenericEvent.class.getSimpleName() + "[", "]")
        .add("message=" + message)
        .add("source=" + source)
        .toString();
  }
}

Once you have the GenericEvent class created, you can next create a listener by annotating the method with @EventListener.

GenericEventListener
@Component
public class GenericEventListener {

  @EventListener
  public void listenEvent(GenericEvent event) {
    System.out.println(event);
  }
}

Now, let us publish few events and test.

JUnit
@SpringJUnitConfig(AppConfig.class)
public class GenericEventTest {

  @Autowired
  private ApplicationEventPublisher eventPublisher;

  @Test
  public void publishEvent() {
    GenericEvent event1 = new GenericEvent<>(this, new Person("John"));
    GenericEvent event2 = new GenericEvent<>(this, "Hello");
    eventPublisher.publishEvent(event1);
    eventPublisher.publishEvent(event2);
  }
}
Person
public class Person {

  private String name;

  public Person(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return new StringJoiner(", ", Person.class.getSimpleName() + "[", "]")
        .add("name='" + name + "'")
        .toString();
  }
}
Remember:

  • Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context.
  • However, for more sophisticated needs you can explore the Spring Integration project.

Spring events offer a powerful mechanism for communication between Spring beans within an application. By leveraging the event publishing and handling infrastructure provided by Spring, developers can facilitate decoupled communication and enhance the modularity and flexibility of their Spring applications.

Through practical examples and code snippets, I demonstrated how to implement custom events and event listeners in a Spring application. By following these examples, readers can gain a deeper understanding of how Spring events work and how they can be effectively utilized in their own projects.

Overall, Spring events provide a flexible and robust solution for inter-bean communication in Spring applications. They promote loose coupling between components, enhance the maintainability and scalability of the codebase, and contribute to the overall robustness and reliability of the application architecture. Readers are encouraged to explore the provided examples and experiment with Spring events in their own applications to unlock the full potential of this powerful feature.

Join our gitter chat room and share your thoughts there.


By |Last Updated: April 2nd, 2024|Categories: Spring Framework|

Table of Contents