Spring provides the ApplicationContext event mechanism to publish and listen to events, which is a very useful feature.

Spring has some built-in events and listeners, such as before the Spring container starts, after the Spring container starts, after the application fails to start, etc. The listeners on these events will respond accordingly.

Of course, we can also customize our listeners to listen to Spring’s original events. Or we can customize our own events and listeners, post the events at the necessary points in time, and then the listeners will respond when they hear the events.

ApplicationContext event mechanism

The ApplicationContext event mechanism is implemented using the observer design pattern, and the ApplicationEvent event class and the ApplicationListener listener interface enable ApplicationContext event publication and processing.

Whenever ApplicationContext issues an ApplicationEvent, if there is an ApplicationListener bean in the Spring container, the listener will be triggered to perform the corresponding processing. Of course, the posting of ApplicationEvent events needs to be shown to be triggered, either by Spring or by us.

ApplicationListener listener

Defines the interface that the application listener needs to implement. This interface inherits from the JDK standard event listener interface EventListener, the EventListener interface is an empty marker interface and it is recommended that all event listeners must inherit from it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package org.springframework.context;

import java.util.EventListener;

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	/**
	 * Handling application events
	 */
	void onApplicationEvent(E event);
}
1
2
3
4
package java.util;

public interface EventListener {
}

ApplicationListener is a generic interface. When we customize the implementation class of this interface, if we specify a specific event class for the generic, then only this event will be listened to. If we do not specify a specific generic type, then we will listen to all subclasses of the ApplicationEvent abstract class.

We define a listener that listens to a specific event, such as the ApplicationStartedEvent event.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Description
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationStartedEvent> {
    @Override
    public void onApplicationEvent(ApplicationStartedEvent event) {
        log.info(">>> MyApplicationListener:{}", event);
    }
}

Start the service and you will find that this listener is triggered after the service is started.

If no specific generic class is specified, all subclass events of the ApplicationEvent abstract class will be listened to. This is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Description
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        log.info(">>> MyApplicationListener:{}", event);
    }
}

Note that the bean of the listener class has to be injected into the Spring container, otherwise it won’t take effect. One way is to use annotation injection, e.g. @Component. Alternatively, you can add them using the SpringApplicationBuilder.listeners() method, but there are differences between these two approaches, see the following example.

First we use the @Component annotation method and when the service starts, 2 events are monitored.

  • ApplicationStartedEvent
  • ApplicationReadyEvent
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Description
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener implements ApplicationListener<SpringApplicationEvent> {
    @Override
    public void onApplicationEvent(SpringApplicationEvent event) {
        log.info(">>> MyApplicationListener:{}", event);
    }
}

While using the SpringApplicationBuilder.listeners() method to add listeners, when the service is started, 5 events are listened to.

  • ApplicationEnvironmentPreparedEvent
  • ApplicationContextInitializedEvent
  • ApplicationPreparedEvent
  • ApplicationStartedEvent
  • ApplicationReadyEvent
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // SpringApplication.run(Application.class, args);

        SpringApplication app = new SpringApplicationBuilder(Application.class)
                .listeners(new MyApplicationListener()).build();
        app.run(args);
    }
}

In fact, this is related to the timing of the listener bean registration, the listener bean of the @component annotation can only be used after the initial registration of the bean, while the listener bean added by SpringApplicationBuilder.listeners() can only be used after the initial registration of the bean. is added before the container starts, so it listens to more events. But note that these two should not be used at the same time, or the listener will be executed twice.

If you want to inject other beans (e.g. @Autowired) into the listener bean, it is better to use annotations, because if you publish the listener too early, other beans may not be initialized yet and may report an error.

ApplicationEvent event

ApplicationEvent is the abstract class that all application events need to inherit from. It inherits from the EventObject class, which is the root class for all events. This class has an object source of type Object, representing the event source. The constructor of all classes that inherit from it must display this event source.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package org.springframework.context;

import java.util.EventObject;

public abstract class ApplicationEvent extends EventObject {

	private static final long serialVersionUID = 7099057708183571937L;

	// System time for publishing events
	private final long timestamp;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	public final long getTimestamp() {
		return this.timestamp;
	}
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package java.util;

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    protected transient Object  source;

    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    public Object getSource() {
        return source;
    }

    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

In Spring, the more important event class is SpringApplicationEvent. Spring has some built-in events that are triggered when some action is completed. These built-in events inherit from the SpringApplicationEvent abstract class. SpringApplicationEvent inherits from ApplicationEvent and adds the string array parameter field args.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}.
 *
 * @author Phillip Webb
 * @since 1.0.0
 */
@SuppressWarnings("serial")
public abstract class SpringApplicationEvent extends ApplicationEvent {

	private final String[] args;

	public SpringApplicationEvent(SpringApplication application, String[] args) {
		super(application);
		this.args = args;
	}

	public SpringApplication getSpringApplication() {
		return (SpringApplication) getSource();
	}

	public final String[] getArgs() {
		return this.args;
	}
}

We can write our own listeners and then listen to these events to implement our own business logic. For example, write an implementation class of the ApplicationListener interface that listens for the ContextStartedEvent event, which will be posted when the application container ApplicationContext starts, so the listener we wrote will be triggered.

  • ContextRefreshedEvent: The event is posted when the ApplicationContext is initialized or refreshed. A call to the refresh() method in the ConfigurableApplicationContext interface also triggers an event posting. Initialization means that all beans are successfully loaded, post-processing Beans are detected and activated, all singleton Beans are pre-instantiated, and the ApplicationContext container is ready for use.
  • ContextStartedEvent: This event is posted after the application context has been refreshed, but before any ApplicationRunner and CommandLineRunner have been called.
  • ApplicationReadyEvent: This event is posted as late as possible to indicate that the application is ready to serve the request. The source of the event is the SpringApplication itself, but care should be taken to modify its internal state, as all initialization steps will have been completed by then.
  • ContextStoppedEvent: The event is posted when stop() of the ConfigurableApplicationContext interface is called to stop the ApplicationContext.
  • ContextClosedEvent: The event is posted when close() of the ConfigurableApplicationContext interface is called to close the ApplicationContext. Note that a closed context cannot be refreshed or restarted after it reaches the end of its lifecycle.
  • ApplicationFailedEvent: The event is posted when the application fails to start.
  • ApplicationEnvironmentPreparedEvent: event is posted when SpringApplication is started and the Environment is first checked and modified when the upper ApplicationContext is not yet created.
  • ApplicationPreparedEvent: The event is published when SpringApplication is starting and ApplicationContext is fully prepared, but not refreshed. At this stage, bean definitions are loaded and the Environment is prepared for use.
  • RequestHandledEvent: This is a web event that can only be applied to Web applications that use a DispatcherServlet. When using Spring as the front-end MVC controller, this event is automatically triggered when Spring finishes processing the user request.

Custom events and listeners

The previous section describes custom listeners and then listening to Springs native events. The following describes custom events and custom listeners, and then publish events in the program to trigger the execution of the listeners and implement your own business logic.

First custom events, inherit from ApplicationEvent, but of course events can be customized with their own properties.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.chenpi;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.context.ApplicationEvent;

/**
 * @Description Custom event
 * @Date 2021/6/26
 * @Version 1.0
 */
@Getter
@Setter
public class MyApplicationEvent extends ApplicationEvent {

    // Additional attributes can be added
    private String myField;

    public MyApplicationEvent(Object source, String myField) {
        super(source); // Binding event sources
        this.myField = myField;
    }

    @Override
    public String toString() {
        return "MyApplicationEvent{" + "myField='" + myField + '\'' + ", source=" + source + '}';
    }
}

Then customize the listener to listen for our custom events.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;

/**
 * @Description Customized listener
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {
    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> MyApplicationListener:{}", event);
    }
}

Registering listeners and posting events. There are two ways to register a listener as explained above. Publishing events can be done with the ApplicationEventPublisher.publishEvent() method. This demonstrates publishing directly with configurableApplicationContext, which implements the ApplicationEventPublisher interface.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package com.chenpi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        // SpringApplication.run(Application.class, args);
        // Register a listener
        SpringApplication app = new SpringApplicationBuilder(Application.class)
                .listeners(new MyApplicationListener()).build();
        ConfigurableApplicationContext configurableApplicationContext = app.run(args);
        // Convenient for demonstration, publish events after the project is started, but of course you can also publish events at other operations and other points in time
        configurableApplicationContext
                .publishEvent(new MyApplicationEvent("I am the event source and publish the event after the project is launched successfully", "I am a custom event property"));
    }
}

Starting the service shows that it is indeed listening to the posted events.

1
2
3
2021-06-26 16:15:09.584  INFO 10992 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8081 (http) with context path ''
2021-06-26 16:15:09.601  INFO 10992 --- [           main] com.chenpi.Application                   : Started Application in 2.563 seconds (JVM running for 4.012)
2021-06-26 16:15:09.606  INFO 10992 --- [           main] com.chenpi.MyApplicationListener         : >>> MyApplicationListener:MyApplicationEvent{myField='I am a custom event property', source=I am the event source and publish the event after the project is launched successfully}

The event listener mechanism can achieve distribution and decoupling effects. For example, you can publish an event in a business class and let the listener listening to the event perform its own business processing.

Example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.chenpi;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
 * @Description
 * @Date 2021/6/26
 * @Version 1.0
 */
@Service
public class MyService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

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

    public void testEvent() {
        applicationEventPublisher
                .publishEvent(new MyApplicationEvent("I am the source of the event", "I am a custom event property"));
    }
}

Annotated listener

In addition to implementing the ApplicationListener interface to create listeners, Spring also provides the annotation @EventListener to create listeners.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description Customized listener
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener01 {
    @EventListener
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> MyApplicationListener:{}", event);
    }
}

The annotation also allows you to filter by condition to listen only to events with the specified condition. For example, an event whose myField property has a value equal to “Chen Pi”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description Customized listener
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener01 {
    @EventListener(condition = "#event.myField.equals('Chen Pi')")
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> MyApplicationListener:{}", event);
    }
}

It is also possible to define multiple listeners in the same class, and to specify the order of the different listeners for the same event. The smaller the value of order, the first to be executed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description Customized listener
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener01 {
    @Order(2)
    @EventListener
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent order=2:{}", event);
    }

    @Order(1)
    @EventListener
    public void onApplicationEvent01(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent order=1:{}", event);
    }

    @EventListener
    public void otherEvent(YourApplicationEvent event) {
        log.info(">>> otherEvent:{}", event);
    }
}

The results of the implementation are as follows.

1
2
3
>>> onApplicationEvent order=1:MyApplicationEvent{myField='Chen Pi', source=我是事件源}
>>> onApplicationEvent order=2:MyApplicationEvent{myField='Chen Pi', source=我是事件源}
>>> otherEvent:MyApplicationEvent{myField='I am custom event property 01', source=I am event source 01}

The event listening processing is synchronized as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.chenpi;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
 * @Description
 * @Date 2021/6/26
 * @Version 1.0
 */
@Service
@Slf4j
public class MyService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

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

    public void testEvent() {
        log.info(">>> testEvent begin");
        applicationEventPublisher.publishEvent(new MyApplicationEvent("I am the source of the event", "Chen Pi"));
        applicationEventPublisher.publishEvent(new YourApplicationEvent("I am event source 01", "I am custom event property 01"));
        log.info(">>> testEvent end");
    }
}

The results of the implementation are as follows.

1
2
3
4
5
2021-06-26 20:34:27.990  INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent begin
2021-06-26 20:34:27.990  INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=1:MyApplicationEvent{myField='Chen Pi', source=I am the source of the event}
2021-06-26 20:34:27.991  INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=2:MyApplicationEvent{myField='Chen Pi', source=I am the source of the event}
2021-06-26 20:34:27.992  INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> otherEvent:MyApplicationEvent{myField='I am custom event property 01', source=I am event source 01}
2021-06-26 20:34:27.992  INFO 12936 --- [nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent end

We can also display the specified asynchronous way to execute the listener, remember to add the @EnableAsync annotation to the service to enable the asynchronous annotation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.chenpi;

import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * @Description Customized listener
 * @Date 2021/6/26
 * @Version 1.0
 */
@Slf4j
@Component
public class MyApplicationListener01 {

    @Async
    @Order(2)
    @EventListener
    public void onApplicationEvent(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent order=2:{}", event);
    }

    @Order(1)
    @EventListener
    public void onApplicationEvent01(MyApplicationEvent event) {
        log.info(">>> onApplicationEvent order=1:{}", event);
    }

    @Async
    @EventListener
    public void otherEvent(YourApplicationEvent event) {
        log.info(">>> otherEvent:{}", event);
    }
}

The execution result is as follows, note the thread name printed.

1
2
3
4
5
2021-06-26 20:37:04.807  INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent begin
2021-06-26 20:37:04.819  INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=1:MyApplicationEvent{myField='Chen Pi', source=I am the source of the event}
2021-06-26 20:37:04.831  INFO 9092 --- [         task-1] com.chenpi.MyApplicationListener01       : >>> onApplicationEvent order=2:MyApplicationEvent{myField='Chen Pi', source=I am the source of the event}
2021-06-26 20:37:04.831  INFO 9092 --- [nio-8081-exec-1] com.chenpi.MyService                     : >>> testEvent end
2021-06-26 20:37:04.831  INFO 9092 --- [         task-2] com.chenpi.MyApplicationListener01       : >>> otherEvent:MyApplicationEvent{myField='I am custom event property 01', source=I am event source 01}

Reference https://www.cnblogs.com/luciochn/p/14940123.html