之前ApplicationRunner
的源码,发现实现此类会注册一个事件到spring容器中,所以随便把spring中事件摸索一下
DEMO
public class User {
private LocalDateTime time;
private String name;
public LocalDateTime getTime() {
return time;
}
public void setTime(LocalDateTime time) {
this.time = time;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class SayHelloEvent extends ApplicationEvent {
private final User user;
public SayHelloEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
}
@Component
public class SayHelloEventListener implements ApplicationListener<SayHelloEvent> {
@Override
public void onApplicationEvent(SayHelloEvent event) {
User user = event.getUser();
String msg = "I am " + user.getName() + " ,It's " + user.getTime() + " now";
System.out.println(msg);
}
}
@RestController
public class PublishEventController {
@Resource
private ApplicationEventPublisher eventPublisher;
@GetMapping("/event")
private ResponseEntity<?> publishEvent() {
User user = new User();
user.setName("Chris!");
user.setTime(LocalDateTime.now());
SayHelloEvent sayHelloEvent = new SayHelloEvent(this, user);
eventPublisher.publishEvent(sayHelloEvent);
return ResponseEntity.ok("OK");
}
}
GET 127.0.0.1:8080/event
输出:
SayHelloEventListener receive SayHelloEvent
I am ForestASpring ,It's 2023-03-02T16:52:37.150 now
需要注意的是:org.springframework.context.ApplicationEventPublisher#publishEvent(org.springframework.context.ApplicationEvent)
方法发布事件之后,将被所有已注册的监听器监听到。所有实现org.springframework.context.ApplicationListener
接口的监听器都将收到该事件并进行处理。
新增监听器 StudyEventListener
@Component
public class StudyEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof SayHelloEvent) {
System.out.println("StudyEventListener receive SayHelloEvent");
} else {
System.out.println("StudyEventListener receive other event");
}
}
}
观察控制台输出,在程序启动时,该监听器就已经监听到事件。说明启动时,程序也发布了一些事件,只被StudyEventListener监听到了,为什么?
StudyEventListener receive other event
StudyEventListener receive other event
2023-03-02 17:27:35.826 INFO 30360 --- [ main] c.f.springboot.DemoApplication : Started DemoApplication in 2.743 seconds (JVM running for 4.04)
StudyEventListener receive other event
StudyEventListener receive other event
StudyEventListener receive other event
StudyEventListener receive other event
测试后得到输出
SayHelloEventListener receive SayHelloEvent
I am ForestASpring ,It's 2023-03-02T17:34:45.563 now
StudyEventListener receive SayHelloEvent
StudyEventListener receive other event
可以看到,明明我们触发了SayHelloEvent
事件,但是为什么 StudyEventListener
类的 other event也被打印了,而SayHelloEventListene
却只打印了一遍,为什么?
显而易见,上面两个监听类其中SayHelloEvent没有指定泛型,所以它可以监听到程序启动时、其他被发布的任何事件,而指定泛型的监听器却只能监听到改事件的发生。
我们从发布事件入手,查看是如何拿到监听器处理事件的。
跟进publishEvent
,进入AbstractApplicationContext
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
....
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 广播事件
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
....
}
跟进multicastEvent
,进入SimpleApplicationEventMulticaster
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Executor executor = this.getTaskExecutor();
// 匹配事件对应的监听器
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
// 遍历执行监听器的方法
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
跟进getApplicationListeners
,进入AbstractApplicationEventMulticaster
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
.....
// 重点关注这两行
// 把传入的事件对象构造成缓存的key,eventType = SayHelloEvent,sourceType=调用publishEvent的Class
AbstractApplicationEventMulticaster.ListenerCacheKey cacheKey = new AbstractApplicationEventMulticaster.ListenerCacheKey(eventType, sourceType);
// 根据key去缓存中匹配对应的value,key是ListenerCacheKey对象,value是CachedListenerRetriever,
// 对象中的applicationListeners属性就是监听器对象列表
// 通过这一步就能匹配到对应的监听器
AbstractApplicationEventMulticaster.CachedListenerRetriever existingRetriever = (AbstractApplicationEventMulticaster.CachedListenerRetriever)this.retrieverCache.get(cacheKey);
......
return this.retrieveApplicationListeners(eventType, sourceType, newRetriever);
}
查看retrieverCache这个MAP是如何存放进去对象的需要查看容器的启动方法,不在这里做说明。
其次是为什么StudyEventListener
会执行两遍,因为在发布事件的时候,spring内部还触发了其他的事件,被StudyEventListener
捕捉到了,所以会比SayHelloEventListene
多打印信息。
另外:
当使用Spring Boot构建应用程序时,除了org.springframework.context.ApplicationListener
接口之外,还可以使用@EventListener
注释来注册事件监听器。@EventListener
注释是ApplicationListener
接口的一个便捷替代品,可以使用它来将方法标记为事件监听器。在这种情况下,如果您想要注销事件监听器,则需要使用removeEventListener()
方法,而不是removeApplicationListener()
方法。