Spring event monitoring mechanism

Spring event monitoring mechanism

This article starts with the event mechanism of JDK, then goes to the event mechanism of Spring and SpringBoot, and analyzes the source code together, and gives the usage method at the same time. The event monitoring mechanism is the same as the observer pattern in the design pattern and is widely used in the Spring framework.

basic concepts

Application events allow us to send and receive specific events, and we can handle these events as needed. Events are used to exchange information between loosely coupled components. Since there is no direct coupling between the publisher and the subscriber, the subscriber can be modified without affecting the publisher, and vice versa. In an event system, there are several important concepts:

  1. Event source: the producer of the event object, any event has a source
  2. Event listener registry: When the event framework or component receives an event, it needs to notify all related event listeners to process it. At this time, there needs to be a place to store the listener, that is, the event listener registry
  3. Event broadcaster: The event broadcaster plays an intermediary role in the entire event mechanism. When the event publisher publishes an event, it needs to notify all relevant listeners to process the event through the broadcaster

JDK event mechanism

In Java, events are described by java.util. EventObject, and event listeners are described by java.util. EventListener.

// 
public class User {
    private Long id;
    public Long getId() {return id;}
    public void setId(Long id) {this.id = id;}
    public User(Long id) {this.id = id;}
}

// 
public class UserEvent extends EventObject {
    public UserEvent(Object source) {super(source);}
}

// 
public class EmailEvent extends UserEvent{
    public EmailEvent(Object source) {super(source);}
}

// 
public interface UserListener extends EventListener {
    // 
    void onRegister(UserEvent event);
}

// 
public class EmailListener implements UserListener {
    @Override
    public void onRegister(UserEvent event) {
        if (event instanceof EmailEvent){
            User user = (User) event.getSource();
            System.out.println(" User Id : " + user.getId() + " ");
        }
    }
}

// 
public class UserService {
    // 
    private final List<UserListener> listeners = new ArrayList<>();
    // 
    public void addListener(UserListener userListener){
        this.listeners.add(userListener);
    }
    // 
    public void register(User user){
        System.out.println(" Id " + user.getId() + " ");
        publishEvent(new EmailEvent(user));
    }
    // 
    private void publishEvent(UserEvent event){
        for (UserListener listener : listeners) {
            listener.onRegister(event);
        }
    }
    public static void main(String[] args) {
        UserService userService = new UserService();
        User user = new User(1000L);
        userService.addListener(new EmailListener());
        userService.register(user);
    }
}

//== ==
// Id 1000 
// User Id : 1000 
 

Spring events

In Spring, the event mechanism is implemented in the observer mode, and the design class diagram is as follows:

  • ApplicationEvent is the top-level abstract class of Spring events, representing the event itself, inherited from EventObject of the JDK. Spring provides some built-in events, such as ContextClosedEvent, ContextStartedEvent, ContextRefreshedEvent, ContextStopedEvent, etc.

  • ApplicationListener is the top-level interface of Spring event listeners. All listeners implement this interface and inherit from the EventListener of the JDK. Spring provides some easy to extend interfaces, such as SmartApplicationListener, GenericApplicationListener

  • ApplicationEventPublisher is Spring's event publishing interface. Event sources publish events through the publishEvent method of this interface. All application contexts have event publishing capabilities, because ApplicationContext inherits this interface

  • ApplicationEventMulticaster is an event broadcaster in the Spring event mechanism. It provides a SimpleApplicationEventMulticaster implementation by default. If the user does not have a custom broadcaster, the default is used (see the refresh method of AbstractApplicationContext for initialization logic). It obtains event listeners from the event registry through the getApplicationListeners method of the parent class AbstractApplicationEventMulticaster, and executes the specific logic of the listeners through the invokeListener method.

The default broadcaster is to call the execution logic of the listener synchronously, but the asynchronous execution of the listener can be realized by configuring the Executor for the broadcaster.

In Spring, the ApplicationContext itself plays the role of the listener registry. In its subclass AbstractApplicationContext, it aggregates the event broadcaster ApplicationEventMulticaster and the event listener ApplicationListnener, and provides the addApplicationListnener method to register the listener.

When an event source generates an event, it publishes the event through the event publisher ApplicationEventPublisher, and then the event broadcaster ApplicationEventMulticaster will go to the event registry ApplicationContext to find the event listener ApplicationListnener, and execute the onApplicationEvent method of the listener one by one to complete the event listener logic.

SpringBoot events

SpringApplicationEvent is the top-level abstract class of SpringBoot events, and SpringBoot has 7 built-in events:

  • ApplicationStartingEvent: Event published when SpringBoot starts
  • ApplicationEnvironmentPreparedEvent: SpringBoot corresponds to the event published when the Environment is prepared. At this time, the context has not been created, and configuration information can be obtained in this event. The default configuration information can be modified by listening to the event, and the configuration center uses this event to complete the remote configuration into the application
  • ApplicationContextInitializedEvent: The event that is published when the context is ready and before any Bean is loaded. The event can get context information
  • ApplicationPreparedEvent: The event published when the context is created. At this time, the Bean has not been fully loaded, but only some specific Beans have been loaded. This event can obtain context information, but cannot obtain custom Beans, which should mean that they have not been loaded yet
  • ApplicationStartedEvent: After the context refresh (complete loading of all Beans), but has not yet called any ApplicationRunner and CommandLineRunner to run the program before the event. The event can also get context information, and can get custom Bean
  • ApplicationReadyEvent: The event published after the completion of ApplicationRunner and CommandLineRunner running program calls, at this time SpringBoot has all started. The event can also get context information
  • ApplicationFailedEvent: Event published when SpringBoot starts abnormally. The event can obtain context information and exception information, and the recycling of resources can be done friendly through this event

The above 7 events are released at the appropriate stage of the container startup. The release sequence is from top to bottom, corresponding to the 7 methods of SpringApplicationRunListener. The source code is as follows:

// SpringBoot 
public interface SpringApplicationRunListener {
    // ApplicationStartingEvent
    default void starting() {
    }
    // ApplicationEnvironmentPreparedEvent
    default void environmentPrepared(ConfigurableEnvironment environment) {
    }
    // ApplicationContextInitializedEvent
    default void contextPrepared(ConfigurableApplicationContext context) {
    }
    // ApplicationPreparedEvent
    default void contextLoaded(ConfigurableApplicationContext context) {
    }
    // ApplicationStartedEvent
    default void started(ConfigurableApplicationContext context) {
    }
    // ApplicationReadyEvent
    default void running(ConfigurableApplicationContext context) {
    }
    // ApplicationFailedEvent
    default void failed(ConfigurableApplicationContext context, Throwable exception) {
    }
}
 

The above interface provides the only implementation class EventPublishingRunListener, which aggregates SpringApplication and SimpleApplicationEventMulticaster. The release of the first 4 events is done by the broadcaster, and the latter 3 delegates to the ApplicationContext to complete the release. In fact, the broadcaster is responsible for publishing in the end.

Similarly, SpringBoot also has a lot of built-in listeners, such as ConfigFileApplicationListener and so on.

How to use the listener

1. Configure via SPI mechanism

Create a new META-INF directory under the resources directory and create a file named spring.factories. In the file, declare the configuration with the full path of the ApplicationListener interface as the key. The configuration content is the full path of the ApplicationListener implementation class. If there are more than one, use a comma Interval, examples are as follows:

org.springframework.context.ApplicationListener=\
com.lyentech.bdc.tuya.listener.HunterApplicationContextInitializedListener,\
com.lyentech.bdc.tuya.listener.HunterApplicationEnvironmentPreparedListener,\
com.lyentech.bdc.tuya.listener.HunterApplicationFailedListener,\
com.lyentech.bdc.tuya.listener.HunterApplicationPreparedListener,\
com.lyentech.bdc.tuya.listener.HunterApplicationReadyListener,\
com.lyentech.bdc.tuya.listener.HunterApplicationStartedListener,\
com.lyentech.bdc.tuya.listener.HunterApplicationStartingListener
 

When Spring starts, it loads the listener implementation class configured in spring.factories, and triggers the logic of the corresponding listener by publishing events at the appropriate stage. This method can achieve the successful loading of the listener for any event (all of the 7 built-in events in SpringBoot).

2. Manually add the main method of the startup class

In the main method of the reference startup class, first create a SpringApplication instance, then call the addListeners method to add a custom listener implementation class, and finally call the run method of the instance to start the container. An example is as follows:

public class TuyaServiceApplication{
	public static void main(String[] args) {
   		// 
        //SpringApplication.run(TuyaServiceApplication.class, args);
        
        // 
        SpringApplication springApplication = new SpringApplication(TuyaServiceApplication.class);
        springApplication.addListeners(new HunterApplicationStartingListener());
        springApplication.addListeners(new HunterApplicationPreparedListener());
        springApplication.run(args);
    }
}
 

This method can also achieve the successful loading of the listener for any event (SpringBoot built-in 7 events are supported).

3. Use @Component

When the listener implementation class is defined, the @Component annotation is applied to annotate the current listener to ensure that Spring can scan it and complete the loading. An example is as follows:

// ApplicationReadyEvent 
@Component
public class HunterApplicationReadyListener implements ApplicationListener<ApplicationReadyEvent> {
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.err.println("===execute  ApplicationReadyEvent  listener...");
    }
}
 

This method can only monitor the last three of the SpringBoot built-in events, because when the container starts to load the Bean with @Component, the first four events have already been released, so they will not take effect.

4. Use @Component and @EventListener

This way can reduce the number of listener classes, an example is as follows:

@Component
public class HunterListener {

    @EventListener
    public void listener1(ApplicationStartingEvent event){
        System.err.println(" @EventListener ApplicationStartingEvent ");
    }

    @EventListener
    public void listener2(ApplicationStartedEvent event){
        System.err.println(" @EventListener ApplicationStartedEvent ");
    }

    @EventListener
    public void listener3(ApplicationReadyEvent event){
        System.err.println(" @EventListener ApplicationReadyEvent ");
    }
}
 

The monitoring of the first four events configured in this way will not take effect for the same reason as the third way.

5. Summary

The listeners of the first three methods need to be defined by implementing the ApplicationListener interface. If there are too many listeners, it will cause a large number of classes; the fourth method requires only one class to complete the definition of multiple listeners. , But the applicable events are limited.

If multiple methods coexist, the listener will be executed multiple times.

If you need to specify the execution order of similar event listeners, you can do it through @Order or implement the Ordered interface.

The listener is executed synchronously by default. If you need to execute asynchronously, one way is to provide an Executor for the broadcaster, and the other is to combine the @Async annotation on the latter two usage methods (at this time, you need to enable the container to support asynchronous through @EnableAsync) .