In depth understanding of Spring event publishing and monitoring

In the application built with Spring, the proper use of event publishing and listening mechanism can make our code more flexible and reduce the degree of coupling.

In the application built with spring, the proper use of event publishing and listening mechanism can make our code more flexible and reduce the degree of coupling. Spring provides a complete event publishing and listening model. In this model, the event publisher only needs to publish events without caring about how many corresponding event listeners there are; The listener does not need to care who published the event, and can listen to the events published by multiple event publishers at the same time. Through this mechanism, event publishing and listening are decoupled.

This section will give examples of the use of event publishing and listening, and introduce the internal implementation principle.

Event release monitoring example

Create a new springboot application, boot version 2.4.0, and introduce the following dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
</dependencies>

Custom event

Spring uses the ApplicationEvent interface to represent an event, so we need to implement the interface for our custom event MyEvent:

public class MyEvent extends ApplicationEvent {
   
    public MyEvent(Object source) {
        super(source);
    }
}

The constructor source parameter represents the event source of the current event, which is generally passed into the context object of Spring.

Event publisher

Event publishing is completed through the event publisher ApplicationEventPublisher. We customize an event publisher MyEventPublisher:

@Component
public class MyEventPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

    private ApplicationContext applicationContext;
    private ApplicationEventPublisher applicationEventPublisher;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishEvent() {
        logger.info("Start publishing custom events MyEvent");
        MyEvent myEvent = new MyEvent(applicationContext);
        applicationEventPublisher.publishEvent(myEvent);
        logger.info("Publish custom events MyEvent end");
    }
}

In the custom event publisher MyEventPublisher, we need to publish events through ApplicationEventPublisher, so we implement the ApplicationEventPublisherAware interface and assign a value to the ApplicationEventPublisher property of MyEventPublisher through the callback method setApplicationEventPublisher; Similarly, our custom event MyEvent constructor needs to pass in the Spring context, so MyEventPublisher also implements the ApplicationContextAware interface and injects the context object ApplicationContext.

The publishEvent method publishes a custom event MyEvent. After the event is released, we then write the corresponding event listener.

Annotation listening

We can easily realize event listening through @ EventListener annotation and write MyEventPublisher:

@Component
public class MyAnnotationEventListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener
    public void onMyEventPublished(MyEvent myEvent) {
        logger.info("Custom event received MyEvent -- MyAnnotationEventListener");
    }
}

The method input parameter marked by the @ EventListener annotation is of type MyEvent, so as long as the MyEvent event event is published, the listener will work, that is, the method will be called back.

Programming monitoring

In addition to using the @ EventListener annotation to realize event listening, we can also manually implement the applicationlister interface to realize event listening (the generic type is the event type of listening):

@Component
public class MyEventListener implements ApplicationListener<MyEvent> {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onApplicationEvent(MyEvent event) {
        logger.info("Custom event received MyEvent");
    }
}

test

Test event publishing in the entry class of springboot:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
        MyEventPublisher publisher = context.getBean(MyEventPublisher.class);
        publisher.publishEvent();
    }
}

Run the program and the output is as follows:

2020-06-22 16:31:46.667  INFO 83600 --- [           main] c.m.demo.publisher.MyEventPublisher      : Start publishing custom events MyEvent
2020-06-22 16:31:46.668  INFO 83600 --- [           main] cc.mrbird.demo.listener.MyEventListener  : Custom event received MyEvent
2020-06-22 16:31:46.668  INFO 83600 --- [           main] c.m.d.l.MyAnnotationEventListener        : Custom event received MyEvent -- MyAnnotationEventListener
2020-06-22 16:31:46.668  INFO 83600 --- [           main] c.m.demo.publisher.MyEventPublisher      : Publish custom events MyEvent end

It can be seen that both listeners have listened to the release of events. In addition, careful readers will find that event publishing and event listening are completed by the same thread. The process is synchronous operation. The event publishing method can be released only after the logic of all corresponding event listeners is executed. Later, we will introduce how to use asynchronous mode to listen to events.

Principle of event release and monitoring

Event release listening process

Make a breakpoint on the event publishing method:

Start the program by debug ging. After the program reaches the breakpoint, click the Step Into button, and the program jumps to the publishEvent(ApplicationEvent event) method of AbstractApplicationContext:

Continue to click Step Into, and the program will jump to the publishevent (object event, @ nullable resolvable type eventtype) method of AbstractApplicationContext:

  1. The getapplicationeventmulticast method is used to obtain the multicast for broadcasting events. The source code is as follows:

    When is the applicationeventmulticast attribute of AbstractApplicationContext assigned? It will be introduced below.

  2. After getting the event multicast device, invoke its multicastEvent method to broadcast the event, click Step Into to enter the method to see the concrete logic:

    View the source code of invokeListener method:

    Continue to view the source code of doInvokeListener method:

The above process is the whole process of event release and monitoring.

Multicast creation process

In order to find out when the applicationeventmulticast attribute of AbstractApplicationContext is assigned (that is, when the event multicast is created), we make a breakpoint on the applicationeventmulticast attribute of AbstractApplicationContext:

Start the program in the way of debug, and the program jumps to the initapplicationeventmulticast method of AbstractApplicationContext:

By tracing the method call stack, we can summarize the process from program execution to the above screenshot:

  1. The main method of the SpringBoot entry class executes springapplication Run (myapplication. Class, args) start the application:

  2. The run method contains the refreshContext method (refresh context):

  3. The refresh method contains the initapplicationeventmulticast method:

  4. The initapplicationeventmulticast method creates a multicast.

Listener acquisition process

In the process of tracking event publishing and listening, we know that the listener corresponding to the event is obtained through getapplicationlisters method:

Method returns the listener corresponding to three MyEvent events. The listener with index 0 is DelegatingApplicationListener. It does not substantially process an event and is ignored; The listener with index 1 is the listener that implements the ApplicationEventListener interface; Listeners with index 2 are listeners implemented through @ EventListener.

Programming listener registration process

View the source code of getapplicationlisters:

Theretrievercache is defined as final map < listenercachekey, cachedlistenerretriever > retrieverCache = new concurrenthashmap < > (64).

Then check the retrieveApplicationListeners method (see the meaning of the method. When the program obtains the listener corresponding to the event for the first time, the cache is empty, so continue to retrieve the listener corresponding to the event):

From the above code, we know that the values of listener collection objects listeners and listener beans used for traversal are from this The applicationListeners and applicationListenerBeans properties of the defaultRetriever are obtained, so we need to pay attention to when these properties are assigned. The type of defaultRetriever is defaultlistener retriever:

Right click the applicationlisters property and select Find Usages to view the assignment related operations:

You can see that the assignment operation occurs in the addApplicationListener method of abstractapplicationeventmulticast,

Continue to right-click the addApplicationListener method and select Find Usages to view the call source:

We make a breakpoint on the registerListeners method, restart the program, and check the method call stack:

From the method call stack, we can summarize this The process of assigning values to the applicationListeners and applicationListenerBeans properties of the defaultretriever:

  1. SpringApplication.run(MyApplication.class, args) starts the Boot program;

  2. Call the refreshContext refresh container method inside the run method:

  3. The registerListener method is called inside the refresh method to register the listener:

  4. The registerListeners method obtains all ApplicationListener type beans from the IOC container, and then assigns them to this The applicationListeners and applicationListenerBeans properties of the defaultretriever.

Annotation listener registration process

View the @ EventListener annotation source code:

View the source code of EventListener methodprocessor:

It implements the smartinitializingsingsingleton interface, which contains the afterSingletonsInstantiated method:

From the annotation, you can see that the calling time of this method is: the single instance Bean is called after instantiation, and the Bean has been created.

Let's see how EventListener methodprocessor implements this method:

Continue to view the source code of processBean method:

So far, the principle of registering listeners in both ways has been clarified.

Use advanced and expanded

Asynchronous event listening

Through the previous analysis, we know that event broadcasting and monitoring are synchronous operations completed by one thread. Sometimes, in order to make broadcasting more efficient, we can consider asynchronizing the event monitoring process.

Single asynchronous

Let's first look at how to realize the asynchrony of a single listener.

First, you need to enable asynchrony through the @ EnableAsync annotation on the springboot entry class, and then use the @ Async annotation annotation on the listener method that needs asynchronous execution. Take MyAnnotationEventListener as an example:

@Component
public class MyAnnotationEventListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Async // asynchronous
    @EventListener
    public void onMyEventPublished(MyEvent myEvent) {
        logger.info("Custom event received MyEvent -- MyAnnotationEventListener");
    }
}

Start the program and the output is as follows:

It can be seen from the log that the listener method has been asynchronized and the execution thread is task-1.

Overall asynchrony

Through the above source code analysis, we know that when multicast broadcasts events, it will first judge whether there is a specified executor, and if so, execute the listener logic through the executor. Therefore, we can make all listening methods execute asynchronously by specifying the executor:

Create a new configuration class:

@Configuration
public class AsyncEventConfigure {

    @Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
    public ApplicationEventMulticaster simpleApplicationEventMulticaster() {
        SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
        eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return eventMulticaster;
    }
}

In the configuration class, we registered a name called abstractapplicationcontext APPLICATION_ EVENT_ MULTICASTER_ Bean_ The Bean of name (i.e. applicationeventmulticast) is used to override the default event multicast, and then TaskExecutor is specified. SimpleAsyncTaskExecutor is the asynchronous task executor provided by Spring.

At present, remove the @ EnableAsync annotation previously added in the springboot entry class, and then start the project. The output is as follows:

As you can see, listener events are asynchronized.

Multi event listener

In addition to listening to a single event, the event listener can also listen to multiple events (only supported by @ EventListener). Modify MyAnnotationEventListener:

@Component
public class MyAnnotationEventListener {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener(classes = {MyEvent.class, ContextRefreshedEvent.class, ContextClosedEvent.class})
    public void onMyEventPublished(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            logger.info("Monitor MyEvent event");
        }
        if (event instanceof ContextRefreshedEvent) {
            logger.info("Monitor ContextRefreshedEvent event");
        }
        if (event instanceof ContextClosedEvent) {
            logger.info("Monitor ContextClosedEvent event");
        }
    }
}

The listener will listen to three types of events: MyEvent, ContextRefreshedEvent and contextcloseedevent:

Listener sorting

Single type events can also be monitored by multiple listeners at the same time. In this case, sorting can be realized by implementing the Ordered interface (or @ Order annotation).

Modify MyEventListener:

@Component
public class MyEventListener implements ApplicationListener<MyEvent>, Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onApplicationEvent(MyEvent event) {
        logger.info("Custom event received MyEvent,My priority is higher");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

Modify MyAnnotationEventListener:

@Component
public class MyAnnotationEventListener implements Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener(classes = {MyEvent.class})
    public void onMyEventPublished(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            logger.info("Monitor MyEvent Event, my priority is lower");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

The output of startup program is as follows:

Fit with SpEL expression

@The EventListener annotation also contains a condition attribute, which can be used in conjunction with the SpEL expression to conditionally trigger the listening method. Modify MyEvent and add a boolean type attribute:

public class MyEvent extends ApplicationEvent {
    
    private boolean flag;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

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

When publishing an event, set this attribute to false:

@Component
public class MyEventPublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

    private ApplicationContext applicationContext;
    private ApplicationEventPublisher applicationEventPublisher;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void publishEvent() {
        logger.info("Start publishing custom events MyEvent");
        MyEvent myEvent = new MyEvent(applicationContext);
        myEvent.setFlag(false); // Set to false
        applicationEventPublisher.publishEvent(myEvent);
        logger.info("Publish custom events MyEvent end");
    }
}

Demonstrate how to use spiel on the @ EventListener annotation of MyAnnotationEventListener:

@Component
public class MyAnnotationEventListener implements Ordered {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @EventListener(classes = {MyEvent.class}, condition = "#event.flag")
    public void onMyEventPublished(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            logger.info("Monitor MyEvent Event, my priority is lower");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

condition = "#event.flag" means to execute when the flag attribute of the current event (here MyEvent) is true.

Start the program and the output is as follows:

Because the flag attribute value of MyEvent released by us is false, the above listener is not triggered.

Transaction event listener

Spring 4.2 began to provide an @ TransactionalEventListener annotation to listen to various stages of database transactions:

  1. AFTER_COMMIT - the transaction is successfully committed;
  2. AFTER_ROLLBACK – after the transaction is rolled back;
  3. AFTER_COMPLETION – after the transaction is completed (whether committed or rolled back);
  4. BEFORE_COMMIT - before the transaction is committed;

example:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onTransactionChange(ApplicationEvent event){
    logger.info("A transaction commit event is detected");
}

Keywords: Spring

Added by dominod on Fri, 28 Jan 2022 04:37:32 +0200