Java 和 Spring 中的事件

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

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

$('#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 应用程序开发提供集成的框架。

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

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    133 引用 • 796 回帖
  • Ngui

    Ngui 是一个 GUI 的排版显示引擎和跨平台的 GUI 应用程序开发框架,基于
    Node.js / OpenGL。目标是在此基础上开发 GUI 应用程序可拥有开发 WEB 应用般简单与速度同时兼顾 Native 应用程序的性能与体验。

    7 引用 • 9 回帖 • 402 关注
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 10 关注
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 447 关注
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    171 引用 • 1537 回帖
  • Postman

    Postman 是一款简单好用的 HTTP API 调试工具。

    4 引用 • 3 回帖 • 2 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 17 关注
  • RemNote
    2 引用 • 16 回帖 • 24 关注
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 3 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1282 回帖 • 5 关注
  • 倾城之链
    23 引用 • 66 回帖 • 166 关注
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 626 关注
  • 小薇

    小薇是一个用 Java 写的 QQ 聊天机器人 Web 服务,可以用于社群互动。

    由于 Smart QQ 从 2019 年 1 月 1 日起停止服务,所以该项目也已经停止维护了!

    35 引用 • 468 回帖 • 765 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    82 引用 • 411 回帖
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 286 关注
  • 程序员

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

    589 引用 • 3528 回帖
  • GAE

    Google App Engine(GAE)是 Google 管理的数据中心中用于 WEB 应用程序的开发和托管的平台。2008 年 4 月 发布第一个测试版本。目前支持 Python、Java 和 Go 开发部署。全球已有数十万的开发者在其上开发了众多的应用。

    14 引用 • 42 回帖 • 820 关注
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    93 引用 • 122 回帖 • 619 关注
  • Quicker

    Quicker 您的指尖工具箱!操作更少,收获更多!

    37 引用 • 157 回帖
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 735 关注
  • Access
    1 引用 • 3 回帖 • 2 关注
  • AWS
    11 引用 • 28 回帖 • 8 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖 • 1 关注
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 7 关注
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    188 引用 • 319 回帖 • 240 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    191 引用 • 1353 回帖