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 bean
s (e.g. @Autowired
) into the listener bean
, it is better to use annotations, because if you publish the listener too early, other bean
s 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 ConfigurableApplicationContex
t interface also triggers an event posting. Initialization means that all beans are successfully loaded, post-processing Bean
s are detected and activated, all singleton Bean
s 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 Spring
s 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