Java的事件模型及Spring的应用

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 的实现方法执行了四次

  1. ContextRefreshedEvent
  2. ServletWebServerInitializedEvent
  3. ApplicationStartedEvent
  4. 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)

参考文章

java事件模型
Java读书笔记12 事件处理基础 Observer设计模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值