Java 和 Spring 中的事件

本贴最后更新于 1452 天前,其中的信息可能已经沧海桑田

事件 简单来说就是发生了什么,然后争对这个发生的操作 怎么处理。比如 页面上有个 按钮,当点击的时候,应该怎么处理。看下下面的代码

$('#btn').on('click',function(){
	console.log('btn click event')
})

通过 js 我们知道,当点击按钮时,会打印 btn click event 这么一句话。

但不点击时,什么也不会发生。

从上可以分析得到

  1. 按钮 这么个东东, 发生的动作会作用在它身上, 我们称 这个 按钮为 事件源
  2. 点击(click) 这么个动作,作用在 事件源 上, 可以将这一过程 抽象为 事件对象。发生的事件中要包含事件源,这样可以在对事件处理的操作中获取到事件源
  3. console.log() 这么个操作,这表示发生了这个事件 应该怎么处理的操作,我们将这一过程抽象为 事件监听

即某个 事件监听器(一个函数或者方法) 会一直监视着某个 事件源(比如按钮) 的某个 操作(比如点击)
当发生相应的动作后,这个监听器就会执行相应的操作。

需要注意的是:

  1. 事件对象中 包含事件源 ,也就说 事件源是事件对象的一个属性
  2. 监听器 需要注册到 事件源上

Java 中的事件

在 Java 中

  • 事件对象 被抽象到 java.util.EventObject , 这个对象中包含一个 Object 的属性,也即 事件源 (可扩展)
  • 事件监听器 被抽象到 java.util.EventListener 中 ,这是一个标识接口,需要自己定义监听方法
  • 事件源 需要自己定义

在 Java 中 模拟 按钮点击事件 如下

事件对象:

public class ClickEventObject extends EventObject {

    /**
     *  这个msg 可随意扩展
     */
    private String msg;

    public ClickEventObject(Object source, String msg) {
        super(source);
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

事件监听器

public interface ClickEventListener extends EventListener {

    /**
     * 事件处理 (我这里是接口)
     * @param eventObject 事件对象
     */
    void handleEvent(EventObject eventObject);
}

事件源

public class Button {

    private ClickEventListener clickEventListener;

    /**
     * 注册 监听器
     * 我这里只是用了单个 EventListener ,你可以改成 List<EventListener> 这样一个事件源就可以注册多个监听器了
     * @param clickEventListener 点击事件监听器
     */
    public void registerListener(ClickEventListener clickEventListener) {
        this.clickEventListener = clickEventListener;
    }

    /**
     * 触发事件
     */
    public void triggerClick() {
        // this 表示事件源本身 这样也就是button
        ClickEventObject clickEventObject = new ClickEventObject(this,"点击事件处理");
        this.clickEventListener.handleEvent(clickEventObject);
    }
}

测试

public static void main(String[] args) {
	// 事件源
	Button button = new Button();

	// 注册监听器
	button.registerListener(new ClickEventListener() {
		// 实例化 监听器处理
		@Override
		public void handleEvent(EventObject eventObject) {
			if (eventObject instanceof ClickEventObject) {
				System.out.println(((ClickEventObject) eventObject).getMsg());
			}
		}
	});

	// 出发点击事件
	button.triggerClick();
}

可以看到

  • 事件对象只是关联事件源和事件监听器的中间对象,它其中包含 事件源
  • 事件监听器需要注册到事件源上

Spring 中的事件

在 spring 的事件中 其实也是扩展了 java 中的 EventListenerEventObject

ApplicationEvent 直接继承了 EventObject

applicationEvent.png

ApplicationListener 直接继承了 EventListener ,并且定义了 onApplicationEvent 方法,需要注意这里使用了泛型指定了特定的 EventObject

applicationListener.png

在 spring 中写一个 按钮 hover 的事件如下

事件对象

public class HoverEvent extends ApplicationEvent {
    /**
     * 自定义的消息 ,这个可以随意扩展
     */
    private String message;

    public HoverEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

事件监听器

@Component
public class HoverListener implements ApplicationListener<HoverEvent> {

    /**
     * 事件处理
     * @param hoverEvent hover事件
     */
    @Override
    public void onApplicationEvent(HoverEvent hoverEvent) {
        System.out.println(hoverEvent.getMessage());
    }
}

事件源

@Component
public class Button {
    @Autowired
    ApplicationContext applicationContext;

    public void triggerHover(String message) {
        applicationContext.publishEvent(new HoverEvent(this,message));
    }
}

测试

public static void main(String[] args) {
	AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);
	Button button = applicationContext.getBean(Button.class);
	button.triggerHover("Hover事件处理");
}

其中 Config.class 是 spring 启动时的 配置类,这里直接将 事件源 和 事件监听器 交给 spring 管理

@ComponentScan("com.slarn.event.spring")
@Configuration
	public class Config {
}

从上面代码可以看到在 事件源 中,注入了 ApplicationContext,触发 triggerHover 后 ,将操作交给了 applicationContext.publishEvent(EventObject) 方法

从这里可以猜测,applicationContext 是触发事件者,在 publishEvent 中必然有 调用 最终的事件处理方法,即最终会调用 HoverListeneronApplicationEvent(EventObject) 方法

跟踪代码可以发现 其调用栈如下:

org.springframework.context.ApplicationEventPublisher#publishEvent(org.springframework.context.ApplicationEvent)
    --> org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
	  --> org.springframework.context.support.AbstractApplicationContext#getApplicationEventMulticaster
	    --> org.springframework.context.event.ApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
		  --> org.springframework.context.event.SimpleApplicationEventMulticaster#invokeListener
		   -- > org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener

在 SimpleApplicationEventMulticaster#doInvokeListener 的方法中 可以看到 最终执行了 listener.onApplicationEvent(event); 方法,完成事件的回调。

到这里你可能会发现一个问题就是,之前说 事件监听器 是要注册到事件源上,但是在上面的 Button 中,却没有这个操作,这是怎么回事呢?是在哪里注册的呢?

带着这个问题,我们重新看下上面的调用栈 ,最终是执行了 listener.onApplicationEvent(event) ,那么这个 listener 是怎么来的呢?

往上找发现有如下代码
org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent()

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	Executor executor = getTaskExecutor();
	for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}

由此可以看到 getApplicationListeners(event, type ) 可以获取到所有的监听器,也可以看出来 一个事件源上 可能有多个监听器。

这里就是 获取所有的监听器,然后遍历执行每个 listener 的 onApplicationEvent 方法

继续跟踪 getApplicationListeners(),发现他是 org.springframework.context.event.AbstractApplicationEventMulticaster#getApplicationListeners() 在这个地方调用的 然后会发现 AbstractApplicationEventMulticaster 其实是 ApplicationEventMulticaster 的实现类,打开这个 ApplicationEventMulticaster 瞄一眼,发现了什么

public interface ApplicationEventMulticaster {

	/**
	 * Add a listener to be notified of all events.
	 * @param listener the listener to add
	 */
	void addApplicationListener(ApplicationListener<?> listener);

	/**
	 * Add a listener bean to be notified of all events.
	 * @param listenerBeanName the name of the listener bean to add
	 */
	void addApplicationListenerBean(String listenerBeanName);

	/**
	 * Remove a listener from the notification list.
	 * @param listener the listener to remove
	 */
	void removeApplicationListener(ApplicationListener<?> listener);

	/**
	 * Remove a listener bean from the notification list.
	 * @param listenerBeanName the name of the listener bean to remove
	 */
	void removeApplicationListenerBean(String listenerBeanName);

	/**
	 * Remove all listeners registered with this multicaster.
	 * <p>After a remove call, the multicaster will perform no action
	 * on event notification until new listeners are registered.
	 */
	void removeAllListeners();

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
	 * if possible as it provides better support for generics-based events.
	 * @param event the event to multicast
	 */
	void multicastEvent(ApplicationEvent event);

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>If the {@code eventType} is {@code null}, a default type is built
	 * based on the {@code event} instance.
	 * @param event the event to multicast
	 * @param eventType the type of event (can be {@code null})
	 * @since 4.2
	 */
	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

其中定义了一个 void addApplicationListener(ApplicationListener<?> listener); 方法,看到这里你会想到什么? 通过名字我们可以猜测 这里有可能就是 注册监听器 的地方,为了验证,我们直接在这方法的实现类上 打上一个断点

这个方法的实现类为
org.springframework.context.event.AbstractApplicationEventMulticaster#addApplicationListener

addApplicationListener.png

如果你对 spring bean 生命周期有一定了解的话,通过调试 跟踪调用栈 可以发现:
在 spring 初始化 bean [实例化,填充属性之后] 的时候(也就是执行 AbstractAutowireCapableBeanFactory#initializeBean()方法)
会在初始化方法调用之后 调用 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

在这个方法中 获取所有的后置处理器(BeanPostProcessor),然后执行每个后置处理器的 postProcessAfterInitialization() 方法

其中有一个后置处理器为 ApplicationListenerDetector,在他的 postProcessAfterInitialization() 方法中 有如下代码

applicationListenerDetetor.png

最终在这个操作里面 把 类型为 ApplicationListener 的 listener 加入到 org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerRetriever#applicationListeners 这个 Set 集合中。

这样在发布(publishEvent)的时候 从这里获取 所有的 listener 然后遍历调用 listener 的 onApplicationEvent 就和上面对应起来了

你可能会问 这个 ApplicationListenerDetector 这个后置处理器 上面为什么能够获取到,其实 这是 spring 内部 手动注册的,注册的代码在 ApplicationContext 刷新 refresh()方式时

org.springframework.context.support.AbstractApplicationContext#registerBeanPostProcessors
 --> org.springframework.context.support.PostProcessorRegistrationDelegate#registerBeanPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, org.springframework.context.support.AbstractApplicationContext)

在这个最后一段代码为:

beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));

另外细心一下你会发现:

  • applicationContext 的 publishEvent 方法 其实是 ApplicationEventPublisher 这个接口指定的方法,表示发布事件 (事件触发者),在 AbstractApplicationContext 中对这方法 进行了实现
  • ApplicationEvent 实现 有 ApplicationContextEvent,ContextRefreshedEvent 这个里面你会发现 source 其实换成了 ApplicationContext,也就是说 ApplicationContext 就是事件源

总结一下:

  1. spring 在实例化 bean 之前 会手动注册一个后置处理器 ApplicationListenerDetector
  2. 在实例化 bean ,填充属性 之后的 初始化 bean 中 initializeBean 方法的 postProcessAfterInitialization 方法中会获取 所有的后置处理器 遍历执行处理,其中就包括上面的 ApplicationListenerDetector
  3. ApplicationListenerDetector 中 会判断 bean 是否是 ApplicationListener,如果是的话就 把这个 bean(listener) 加入到 org.springframework.context.event.AbstractApplicationEventMulticaster.ListenerRetriever#applicationListeners 这个 Set 集合中
  4. AbstractApplicationEventMulticaster 这个类 是 AbstractApplicationContext 抽象类中的一个 属性(并且不为空,在 refresh()方法的 initApplicationEventMulticaster() 中初始化了一个 SimpleApplicationEventMulticaster 的多播器),也就是说 ApplicationContext 有 AbstractApplicationEventMulticaster,即也能能获取到所有的 listeners
  5. ApplicationContext 继承了 ApplicationEventPublisher,同时 AbstractApplicationContext 也实现了 ApplicationEventPublisher 的 publishEvent
  6. 在这个 publishEvent() 方法中就获取到 所有的 listeners 然后遍历 执行 listener.onApplicationEvent(event);
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    942 引用 • 1459 回帖 • 96 关注
1 操作
jchain 在 2020-09-02 11:15:49 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 1 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 4 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 656 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 623 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    19 引用 • 31 回帖
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    14 引用 • 7 回帖
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖 • 12 关注
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    89 引用 • 345 回帖
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 10 关注
  • 钉钉

    钉钉,专为中国企业打造的免费沟通协同多端平台, 阿里巴巴出品。

    15 引用 • 67 回帖 • 352 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 327 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 614 关注
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 202 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    126 引用 • 1699 回帖
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 2 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 526 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 18 关注
  • OnlyOffice
    4 引用 • 15 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    546 引用 • 3531 回帖 • 1 关注
  • SQLite

    SQLite 是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。SQLite 是全世界使用最为广泛的数据库引擎。

    4 引用 • 7 回帖 • 2 关注
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    20643 引用 • 80670 回帖 • 1 关注
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    82 引用 • 37 回帖
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 698 关注
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 364 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    62 引用 • 289 回帖