Java 和 Spring 中的事件

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

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

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

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

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 772 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    6 引用 • 63 回帖 • 5 关注
  • 心情

    心是产生任何想法的源泉,心本体会陷入到对自己本体不能理解的状态中,因为心能产生任何想法,不能分出对错,不能分出自己。

    59 引用 • 369 回帖
  • CentOS

    CentOS(Community Enterprise Operating System)是 Linux 发行版之一,它是来自于 Red Hat Enterprise Linux 依照开放源代码规定释出的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定的服务器以 CentOS 替代商业版的 Red Hat Enterprise Linux 使用。两者的不同在于 CentOS 并不包含封闭源代码软件。

    238 引用 • 224 回帖
  • 快应用

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

    15 引用 • 127 回帖
  • abitmean

    有点意思就行了

    27 关注
  • 大数据

    大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产。

    93 引用 • 113 回帖
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 588 回帖
  • GitBook

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

    3 引用 • 8 回帖
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 548 关注
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 2 关注
  • 倾城之链
    23 引用 • 66 回帖 • 138 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    135 引用 • 190 回帖
  • Android

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

    334 引用 • 323 回帖 • 4 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖
  • 脑图

    脑图又叫思维导图,是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一种实用性的思维工具。

    30 引用 • 96 回帖 • 1 关注
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    98 引用 • 344 回帖
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖
  • HHKB

    HHKB 是富士通的 Happy Hacking 系列电容键盘。电容键盘即无接点静电电容式键盘(Capacitive Keyboard)。

    5 引用 • 74 回帖 • 478 关注
  • Sublime

    Sublime Text 是一款可以用来写代码、写文章的文本编辑器。支持代码高亮、自动完成,还支持通过插件进行扩展。

    10 引用 • 5 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    26 引用 • 196 回帖 • 17 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    55 引用 • 85 回帖
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    313 引用 • 547 回帖
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    5 引用 • 15 回帖 • 102 关注
  • BookxNote

    BookxNote 是一款全新的电子书学习工具,助力您的学习与思考,让您的大脑更高效的记忆。

    笔记整理交给我,一心只读圣贤书。

    1 引用 • 1 回帖
  • Wide

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

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

    30 引用 • 218 回帖 • 635 关注