Java 事件模型
1 事件模型的定义
平时在看 Spring 源码时会注意到很多带有 XxxListener 的方法,例如:ApplicationListener。并且这类方法都包含 ApplicationEvent 这样以 XxxEvent结尾的对象。其实它就是 java 从 jdk1.1 开始使用的事件模型,jdk 的事件模型包含三种角色,分别是
- Event Eource :事件源对象,可以注册事件监听器并发送事件对象
- Event Object :事件状态对象,一般是一个动作
- Event Listener :事件监听器对象,对事件变化的监听
用一个例子来解释这三种角色,你一个人住在家里,当有人来你家做客时,会按响门口的门铃,你便知道有客人来访,可以起身去开门了。在这一个事件模型中,门铃便是 Event Eource 事件源,按下门铃发出响声就是 Event Object 事件状态对象,Event Listener 监听器便是监听门铃是否有响声
2 Spring 中的事件模型
2.1 ApplicationEvent
在 Spring 中,它提供了一个关于事件对象的顶级抽象类 ApplicationEvent (关于这类的描述如下:Class to be extended by all application events. Abstract as it doesn’t make sense for generic events to be published directly/简单地说就是供所有事件对象做扩展用),目的是方便所有人进行扩展使用,而 ApplicationEvent 继承于 java.util.EventObject 这个 jdk 提供的 root 类(关于这类的描述如下:The root class from which all event state objects shall be derived/派生所有事件对象的root类),所以我们在定义一个 Event Object 的时候一般类名是 XxxEvent,其中 Xxx 即表示事件
package org.springframework.context;
import java.util.EventObject;
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp;
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
public final long getTimestamp() {
return this.timestamp;
}
}
2.2 ApplicationListener
在 Spring 中,它提供了一个关于事件监听的顶级接口 ApplicationListener(关于这个接口的描述:Interface to be implemented by application event listeners.Based on the standard {@code java.util.EventListener} interface for the Observer design pattern/它是所有监听的实现接口,实现方式是基于 EventListener 采用了观察者设计模式),所以本质上 ApplicationListener 是继承了接口 EventListener(关于这个接口的描述:A tagging interface that all event listener interfaces must extend/所有事件侦听器接口都必须扩展的标记接口)并添加了对监听的处理事件方法。所以我们一般在定义一个监听的时候,一般接口是 XxxListener,其中 Xxx 表示事件
package org.springframework.context;
import java.util.EventListener;
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
2.3 AbstractApplicationContext
在 spring 中,事件环境就是 spring 的容器管理的抽象方法 AbstractApplicationContext
AbstractApplicationContext 实现了接口 ConfigurableApplicationContext 的 addApplicationListener() 方法(关于这个方法的描述:Add a new ApplicationListener that will be notified on context events such as context refresh and context shutdown/添加一个ApplicationListener监听用于在上下文变化时收到通知)
ConfigurableApplicationContext 又继承了接口 ApplicationContext,而 ApplicationContext 接口则继承了接口 ApplicationEventPublisher,接口 ApplicationEventPublisher 只有两个方法就是 publishEvent(ApplicationEvent event)方法和publishEvent(Object event)方法(关于这个方法的描述:Notify all listeners registered with this application of an event/通知所有注册事件的变化)
所以经过一系列的实现,事件环境就拥有了注册监听 addApplicationListener() 和通知监听变化 publishEvent() 的能力
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
/** Helper class used in event publishing */
private ApplicationEventMulticaster applicationEventMulticaster;
/** Statically specified listeners */
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<ApplicationListener<?>>();
/** ApplicationEvents published early */
private Set<ApplicationEvent> earlyApplicationEvents;
//触发事件
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
//触发事件
@Override
public void publishEvent(Object event) {
publishEvent(event, null);
}
//触发事件
protected void publishEvent(Object event, ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<Object>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
if (this.applicationEventMulticaster == null) {
throw new IllegalStateException("ApplicationEventMulticaster not initialized - " +
"call 'refresh' before multicasting events via the context: " + this);
}
return this.applicationEventMulticaster;
}
//添加事件监听器
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
Assert.notNull(listener, "ApplicationListener must not be null");
if (this.applicationEventMulticaster != null) {
this.applicationEventMulticaster.addApplicationListener(listener);
}
else {
this.applicationListeners.add(listener);
}
}
//注册事件监听器
protected void registerListeners() {
// Register statically specified listeners first.
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// Publish early application events now that we finally have a multicaster...
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
//刷新ApplicationContext的时候触发ContextStartedEvent
protected void finishRefresh() {
// Initialize lifecycle processor for this context.
initLifecycleProcessor();
// Propagate refresh to lifecycle processor first.
getLifecycleProcessor().onRefresh();
// Publish the final event.
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
//启动时触发ContextStartedEvent
@Override
public void start() {
getLifecycleProcessor().start();
publishEvent(new ContextStartedEvent(this));
}
//关闭时触发ContextStartedEvent
@Override
public void stop() {
getLifecycleProcessor().stop();
publishEvent(new ContextStoppedEvent(this));
}
}
ApplicationListener 的用法
- 实现 ApplicationListener 接口
简单的应用就是实现 ApplicationListener 接口的 onApplicationEvent(ApplicationEvent applicationEvent)方法即可
@Component
public class TestApplicationListener implements ApplicationListener {
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent) {
System.out.println(applicationEvent.toString());
System.out.println("TestApplicationListener 初始化了。。。");
}
}
结果
2020-05-27 20:45:37,780main com.alibaba.druid.pool.DruidDataSource {dataSource-1} inited
2020-05-27 20:45:37,944main org.hibernate.dialect.Dialect HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
2020-05-27 20:45:38,060main o.h.e.t.j.p.i.JtaPlatformInitiator HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-05-27 20:45:38,648main o.s.o.j.LocalContainerEntityManagerFactoryBean Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-05-27 20:45:38,656TestInitlizingBean 初始化了。。。
main org.neo4j.ogm.metadata.DomainInfo Starting Post-processing phase
2020-05-27 20:45:39,268main org.neo4j.ogm.metadata.DomainInfo Building byLabel lookup maps
2020-05-27 20:45:39,268main org.neo4j.ogm.metadata.DomainInfo Building interface class map for 20 classes
2020-05-27 20:45:39,269main org.neo4j.ogm.metadata.DomainInfo Post-processing complete
2020-05-27 20:45:39,280main o.s.d.neo4j.mapping.Neo4jMappingContext Neo4jMappingContext initialisation completed
2020-05-27 20:45:39,354main o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-05-27 20:45:39,420main o.s.b.a.d.n.Neo4jDataAutoConfiguration$Neo4jWebConfiguration spring.data.neo4j.open-in-view is enabled by default.Therefore, database queries may be performed during view rendering. Explicitly configure spring.data.neo4j.open-in-view to disable this warning
2020-05-27 20:45:39,424main o.s.s.concurrent.ThreadPoolTaskExecutor Initializing ExecutorService 'applicationTaskExecutor'
2020-05-27 20:45:39,646org.springframework.context.event.ContextRefreshedEvent[source=org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@74e52ef6, started on Wed May 27 20:45:34 CST 2020]
TestApplicationListener 初始化了。。。
main o.s.b.w.embedded.tomcat.TomcatWebServer Tomcat started on port(s): 9317 (http) with context path ''
2020-05-27 20:45:39,883org.springframework.boot.web.servlet.context.ServletWebServerInitializedEvent[source=org.springframework.boot.web.embedded.tomcat.TomcatWebServer@1c68d0db]
TestApplicationListener 初始化了。。。
main com.just.JustApplication Started JustApplication in 5.866 seconds (JVM running for 7.298)
2020-05-27 20:45:39,885org.springframework.boot.context.event.ApplicationStartedEvent[source=org.springframework.boot.SpringApplication@6734ff92]
TestApplicationListener 初始化了。。。
{"nonOptionArgs":[],"optionNames":["success.start!!!"],"sourceArgs":["--success.start!!!"]}
["--success.start!!!"]
org.springframework.boot.context.event.ApplicationReadyEvent[source=org.springframework.boot.SpringApplication@6734ff92]
TestApplicationListener 初始化了。。。
可以看出 ApplicationListener 的执行顺序时晚于 InitlizingBean 的。但是很有意思的是 ApplicationListener 的实现方法执行了四次
- ContextRefreshedEvent
- ServletWebServerInitializedEvent
- ApplicationStartedEvent
- ApplicationReadyEvent
分别是这四个事件的监听变化时初通知了 ApplicationListener,然后执行了所有监听的实现方法,所以如果我们想只执行一次的话,可以找到 ApplicationListener 监听的事件,然后指定其只打印一次
@Component
public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent applicationEvent) {
if(applicationEvent.getApplicationContext().getParent() == null){
System.out.println("TestApplicationListener 初始化了。。。");
}
}
}
结果
2020-05-27 21:11:30,107main com.alibaba.druid.pool.DruidDataSource {dataSource-1} inited
2020-05-27 21:11:30,335main org.hibernate.dialect.Dialect HHH000400: Using dialect: org.hibernate.dialect.MySQL57Dialect
2020-05-27 21:11:30,504main o.h.e.t.j.p.i.JtaPlatformInitiator HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-05-27 21:11:31,558main o.s.o.j.LocalContainerEntityManagerFactoryBean Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-05-27 21:11:31,578TestInitlizingBean 初始化了。。。
main org.neo4j.ogm.metadata.DomainInfo Starting Post-processing phase
2020-05-27 21:11:33,159main org.neo4j.ogm.metadata.DomainInfo Building byLabel lookup maps
2020-05-27 21:11:33,159main org.neo4j.ogm.metadata.DomainInfo Building interface class map for 22 classes
2020-05-27 21:11:33,160main org.neo4j.ogm.metadata.DomainInfo Post-processing complete
2020-05-27 21:11:33,191main o.s.d.neo4j.mapping.Neo4jMappingContext Neo4jMappingContext initialisation completed
2020-05-27 21:11:33,388main o.s.b.a.o.j.JpaBaseConfiguration$JpaWebConfiguration spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-05-27 21:11:33,565main o.s.b.a.d.n.Neo4jDataAutoConfiguration$Neo4jWebConfiguration spring.data.neo4j.open-in-view is enabled by default.Therefore, database queries may be performed during view rendering. Explicitly configure spring.data.neo4j.open-in-view to disable this warning
2020-05-27 21:11:33,574main o.s.s.concurrent.ThreadPoolTaskExecutor Initializing ExecutorService 'applicationTaskExecutor'
2020-05-27 21:11:33,776TestApplicationListener 初始化了。。。
main o.s.b.w.embedded.tomcat.TomcatWebServer Tomcat started on port(s): 9317 (http) with context path ''
2020-05-27 21:11:34,068main com.just.JustApplication Started JustApplication in 9.615 seconds (JVM running for 11.327)