Java 和 Spring 中的事件

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

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

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

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

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • 资讯

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

    55 引用 • 85 回帖
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1434 引用 • 10054 回帖 • 490 关注
  • Lute

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

    25 引用 • 191 回帖 • 16 关注
  • 钉钉

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

    15 引用 • 67 回帖 • 339 关注
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    75 引用 • 1737 回帖 • 5 关注
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    17 引用 • 53 回帖 • 136 关注
  • 微服务

    微服务架构是一种架构模式,它提倡将单一应用划分成一组小的服务。服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中。服务于服务之间才用轻量级的通信机制互相沟通。每个服务都围绕着具体业务构建,能够被独立的部署。

    96 引用 • 155 回帖 • 1 关注
  • 招聘

    哪里都缺人,哪里都不缺人。

    190 引用 • 1057 回帖
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    85 引用 • 139 回帖 • 1 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 1 关注
  • Eclipse

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

    75 引用 • 258 回帖 • 617 关注
  • 互联网

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

    98 引用 • 344 回帖
  • 开源中国

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

    7 引用 • 86 回帖
  • 创业

    你比 99% 的人都优秀么?

    84 引用 • 1399 回帖
  • SVN

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

    29 引用 • 98 回帖 • 680 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 787 关注
  • 心情

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

    59 引用 • 369 回帖
  • 新人

    让我们欢迎这对新人。哦,不好意思说错了,让我们欢迎这位新人!
    新手上路,请谨慎驾驶!

    52 引用 • 228 回帖
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    21 引用 • 245 回帖 • 241 关注
  • jQuery

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

    63 引用 • 134 回帖 • 724 关注
  • wolai

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

    2 引用 • 14 回帖
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    287 引用 • 4484 回帖 • 669 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 22 关注
  • 国际化

    i18n(其来源是英文单词 internationalization 的首末字符 i 和 n,18 为中间的字符数)是“国际化”的简称。对程序来说,国际化是指在不修改代码的情况下,能根据不同语言及地区显示相应的界面。

    8 引用 • 26 回帖
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 595 关注