Spring Event event driven

Spring event driven model is similar to Pub/Sub publish / subscribe mode in message queue and observer mode in Java design mode.

Custom events

The event interface of Spring is located at org.springframework.context.ApplicationEvent. The source code is as follows:

public abstract class ApplicationEvent extends EventObject {
    private static final long serialVersionUID = 7099057708183571937L;
    private final long timestamp;
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }
    public final long getTimestamp() {
        return this.timestamp;
    }
}

The event object of Java is inherited, so the getSource() method can be used to get the event propagation object.

Custom Spring events

public class CustomSpringEvent extends ApplicationEvent {
    private String message;

    public CustomSpringEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

Then define the event listener, which is actually equivalent to the consumer and needs to be handed over to the Spring container for management.

@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }
}

Finally define the event publisher

@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

Create test class

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomSpringEventPublisherTest {

    @Autowired
    private CustomSpringEventPublisher publisher;

    @Test
    public void publishStringEventTest() {
        publisher.doStuffAndPublishAnEvent("111");
    }
}

Run the test class, and you can see that the console prints two important messages

//Release events
Publishing custom event. 
//The listener gets the event and processes it accordingly
Received spring custom event - 111

Since Spring events are the publish / subscribe mode, there are three situations in the publish / subscribe mode

  1. 1 producer - 1 consumer
  2. 1 producer - Multi consumer
  3. Multi producer multi consumer

The example above is the first case. Let's try the other two cases

Continue to create an event listener as a consumer:

@Component
public class CustomSpringEventListener2 implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("CustomSpringEventListener2 Received spring custom event - " + event.getMessage());
    }
}

After running the test class, you can see that the console prints two pieces of consumption information in sequence:

Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111

Note: Spring's publish and subscribe mode is broadcast mode. All consumers can receive messages and consume normally

Try the third multi producer multi consumer scenario

Continue to create a publisher,

@Component
public class CustomSpringEventPublisher2 {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("CustomSpringEventPublisher2 Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

Console output:

CustomSpringEventPublisher Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
CustomSpringEventPublisher2 Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 222
CustomSpringEventListener2 Received spring custom event - 222

From the above output, we can guess that Spring's event publishing and subscription mechanism is synchronous, that is to say, the event must be consumed by all consumers before the publisher's code can continue to move down. This is obviously not the effect we want. Is there any event executed asynchronously?

Asynchronous events in Spring

To use Spring's asynchronous events, we need to customize the asynchronous event configuration class

@Configuration
public class AsynchronousSpringEventsConfig {
    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster
                = new SimpleApplicationEventMulticaster();

        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

The publish and subscribe codes do not need to be changed. Run the test class directly. The console will print out:

CustomSpringEventPublisher Publishing custom event. 
CustomSpringEventPublisher2 Publishing custom event. 
CustomSpringEventListener1 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 111
CustomSpringEventListener2 Received spring custom event - 222
CustomSpringEventListener1 Received spring custom event - 222

As you can see, the two publishers run almost at the same time, which proves that the listener executes asynchronously and does not block the publisher's code. To be precise, the listener will process events asynchronously in a single thread.

Spring's own event types

Event driven is widely used in Spring. We can find many event events by looking at the subclass of ApplicationEvent, which will not be covered here.

Annotation driven listener

Starting from Spring 4.2, event listeners do not need to be beans that implement the ApplicationListener interface. They can be annotated with @ EventListener on any public method of beans managed by Spring containers.

@Component
public class AnnotationDrivenContextStartedListener {
    @EventListener
    public void handleContextStart(CustomSpringEvent cse) {
        System.out.println("Handling Custom Spring Event.");
    }
}

Console output:

CustomSpringEventPublisher Publishing custom event.
Handling Custom Spring Event.
CustomSpringEventPublisher2 Publishing custom event. 
Handling Custom Spring Event.

Similarly, we can see that this event listener is executed synchronously. If you want to change it to asynchronous listener, add @ Async to the event method, and turn on Asynchronous Support in Spring application (add @ enableasync to SpringBootApplication).

@Component
public class AnnotationDrivenContextStartedListener {
    @Async
    @EventListener
    public void handleContextStart(CustomSpringEvent cse) {
        System.out.println("Handling Custom Spring Event.");
    }
}

Run the test class again:

CustomSpringEventPublisher Publishing custom event. 
CustomSpringEventPublisher2 Publishing custom event. 
Handling Custom Spring Event.
Handling Custom Spring Event.

Generic support

Create a generic event model

@Data
public class GenericSpringEvent<T> {
    private T message;
    protected boolean success;

    public GenericSpringEvent(T what, boolean success) {
        this.message = what;
        this.success = success;
    }
}

Note the difference between generic spring event and custom spring event. We now have the flexibility to publish any arbitrary event and no longer need to extend from ApplicationEvent.

In this way, we can't define a listener by inheriting ApplicationListener as before, because ApplicationListener defines that the event must be a subclass of ApplicationEvent. Therefore, we can only use annotation driven listeners.

You can also make event listeners conditional by defining Boolean spiel expressions on the @ EventListener annotation. In this case, the event handler will only be called for the GenericSpringEvent of the successful String:

@Component
public class AnnotationDrivenEventListener {
    @EventListener(condition = "#event.success")
    public void handleSuccessful(GenericSpringEvent<String> event) {
        System.out.println("Handling generic event (conditional).");
    }
}

Define specific types of events:

public class StringGenericSpringEvent extends GenericSpringEvent<String> {
    public StringGenericSpringEvent(String message, boolean success) {
        super(message, success);
    }
}

Define Publisher:

@Component
public class StringGenericSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message, final boolean success) {
        System.out.println("CustomSpringEventPublisher Publishing custom event. ");
        StringGenericSpringEvent springEvent = new StringGenericSpringEvent(message, success);
        applicationEventPublisher.publishEvent(springEvent);
    }
}

Test class:

@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomSpringEventPublisherTest {

    @Autowired
    private StringGenericSpringEventPublisher publisher;

    @Test
    public void publishStringEventTest() {
        publisher.doStuffAndPublishAnEvent("success", true);
        publisher.doStuffAndPublishAnEvent("failed", false);
    }
}

Operation result:

CustomSpringEventPublisher Publishing custom event. 
Handling generic event (conditional) success
CustomSpringEventPublisher Publishing custom event. 

The listener only deals with the success events, and the success ignores the failure events. The advantage is that you can define different actions for success and failure for the same event.

Transaction binding of Spring events

Starting from Spring 4.2, the framework provides a new @ TransactionalEventListener annotation, which is an extension of @ EventListener, allowing the listener of events to be bound to a stage of a transaction. Binding can perform the following transaction phases:

  • After? Commit (default): triggered after the transaction succeeds
  • After? Rollback: triggered when a transaction is rolled back
  • After? Completion: triggered after the transaction is completed, regardless of whether it is successful or not
  • Before commit: triggered before transaction commit

summary

  1. The basics of handling events in Spring: create a simple custom event, publish it, and then process it in the listener.
  2. Enable asynchronous processing of events in the configuration.
  3. Improvements introduced in Spring 4.2, such as annotation driven listeners, better generic support, and events bound to the transaction phase.

👉 Article code address

Tags: Java Spring

Posted on Mon, 10 Feb 2020 07:00:38 -0500 by Solar