[spring源码学习]九、IOC源码-applicationEventMulticaster事件广播
一、代码实例
回到第IOC的第七章context部分,我们看源码分析部分,可以看到在spring的bean加载之后的第二个重要的bean为applicationEventMulticaster,从字面上我们知道它是一个事件广播器。在第8和9部分,详细描述了广播器的初始化:
1、查找是否有name为applicationEventMulticaster的bean,如果有放到容器里,如果没有,初始化一个系统默认的放入容器
2、查找手动设置的applicationListeners,添加到applicationEventMulticaster里
3、查找定义的类型为ApplicationListener的bean,设置到applicationEventMulticaster
4、初始化完成、对earlyApplicationEvents里的事件进行通知(此容器仅仅是广播器未建立的时候保存通知信息,一旦容器建立完成,以后均直接通知)
5、在系统操作时候,遇到的各种bean的通知事件进行通知
以上流程我们在第七章源码部分都已经分析过了,所以不再赘述。可以看到的是applicationEventMulticaster是一个标准的观察者模式,对于他内部的监听者applicationListeners,每次事件到来都会一一获取通知。
我们来进行实例展示:
1、定义一个ApplicationEvent,
package com.zjl; import org.springframework.context.ApplicationEvent; public class MyApplicationEvent extends ApplicationEvent { /**
*
*/
private static final long serialVersionUID = 1L; public MyApplicationEvent(Object source) {
super(source);
} }
2、定义一个事件处理器MyApplicationListener,仅仅监听我们自定义的事件,注:此处的泛型如果不指定或者指定ApplicationEvent,可以监听所有spring发出的监听
package com.zjl; import org.springframework.context.ApplicationListener; public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> { @Override
public void onApplicationEvent(MyApplicationEvent event) { System.out.println(event.getSource()+"==== "+event.getTimestamp());
} }
3、测试代码
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
context.publishEvent(new MyApplicationEvent("init-bean"));
Person person=(Person)context.getBean("person");
person.sayHello();
}
}
4、结果(成功的打印出事件的内容)
init-bean==== 1462785633657
hello zhangsan
二、源码分析
1、执行publishEvent,支持两种事件1、直接继承ApplicationEvent,2、其他时间,会被包装为PayloadApplicationEvent,可以使用getPayload获取真实的通知内容
如果广播器未生成,先存起来,已经生成的调用multicastEvent进行发送
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;
//支持两种事件1、直接继承ApplicationEvent,2、其他时间,会被包装为PayloadApplicationEvent,可以使用getPayload获取真实的通知内容
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) {
//如果有预制行添加到预制行,预制行在执行一次后被置为null,以后都是直接执行
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//广播event时间
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
} // Publish event via parent context as well...
//父bean同样广播
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
2、查找所有的监听者,依次遍历,如果有线程池,利用线程池进行发送,如果没有则直接发送,如果针对比较大的并发量,我们应该采用线程池模式,将发送通知和真正的业务逻辑进行分离
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
invokeListener(listener, event);
}
}
}
3、在获取监听器的过程中调用,,可以看到监听器根据两种类型判断是否需要处理:
a)、继承SmartApplicationListener的情况下,根据supportsEventType返回结果判断
b)、根据监听器的泛型判断(示例采用第二种方式),比较泛型的代码很复杂,暂时就不关心了
public boolean supportsEventType(ResolvableType eventType) {
//判断监听器是否可以监听事件有两种方式:
//1、如果监听器是SmartApplicationListener的实现,那么会重写supportsEventType,返回true就是支持
if (this.delegate instanceof SmartApplicationListener) {
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.getRawClass();
return ((SmartApplicationListener) this.delegate).supportsEventType(eventClass);
}
else {
//根据泛型内容与事件类型是否一致
return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
}
}
3、调用invokeListener,如果有errorHandler会有errorHandler处理异常
protected void invokeListener(ApplicationListener listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
listener.onApplicationEvent(event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
// Possibly a lambda-defined listener which we could not resolve the generic event type for
LogFactory.getLog(getClass()).debug("Non-matching event type for listener: " + listener, ex);
}
}
}
4、到此为止,我们完成了广播器的代码跟踪
三、总结
广播器的代码不算复杂,使用了一个标准的观察者模式,系统在初始化的时候会在context中注册一个applicationListeners,如果系统有需要广播的情况下,会发送一个applicationEvent事件,注册的listener会根据自己关心的类型进行接收和解析。
在我们之前的实力中,我们需要补充的主要有三点:
1、监听器对于事件的处理,系统中有两种方式:
a)、继承SmartApplicationListener的情况下,根据supportsEventType返回结果判断
b)、根据监听器的泛型判断
c)、我们很容易想到,如果没有继承父类,也没有泛型的情况下,我们可以在广播器的onApplicationEvent方法中获取到event,然后自行过滤
2、事件通知往往是独立于整个程序运行之外的一个补充,所以另外开启线程进行工作是个不错的办法,listener中提供了executor的注入来实现多线程
3、事件的类型不仅仅支持ApplicationEvent类型,也支持其他类型,spring会自动转化为PayloadApplicationEvent,我们调用它的getPayload将获取到具体的数据
4、可以注入一个errorHandler,完成对异常的处理
四、示例修改
1、修改配置文件
<bean name="MyApplicationListener" class="com.zjl.MyApplicationListener">
</bean>
<!-- 定义一个固定大小的线程,采用factory-method和静态方法的形式,参数注入使用构造函数注入 -->
<bean name="executor" class="java.util.concurrent.Executors" factory-method="newFixedThreadPool">
<constructor-arg index="0"><value>5</value></constructor-arg>
</bean>
<!-- 定义applicationEventMulticaster,注入线程池和errorHandler,此处使用系统自带的广播器,也可以注入其他广播器, -->
<bean name="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
<property name="taskExecutor" ref="executor"></property>
<property name="errorHandler" ref="errorHandler"></property>
</bean>
<!-- 定义一个errorHandler,统一处理异常信息 -->
<bean name="errorHandler" class="com.zjl.MyErrorHandler"></bean>
2、异常处理的errorHandler
public class MyErrorHandler implements ErrorHandler { @Override
public void handleError(Throwable t) {
System.out.println("捕获到了异常:"+t.getMessage());
} }
3、修改MyApplicationListener ,使它继承SmartApplicationListener,主要做以下处理:
a)supportsSourceType-判断广播来源类的类型,全部返回true,表示接收所有类的广播
b)supportsEventType-接收PayloadApplicationEvent类型,也就是非event类型的广播;自定义的广播
c)为了使errorHandler起作用,用了一个1/0,使系统抛出一个异常,注:此处由于onApplicationEvent本身接口没有抛出异常,所以显式的异常系统编译都会有问题,所以感觉并不好用
public class MyApplicationListener implements SmartApplicationListener{ @Override
public void onApplicationEvent(ApplicationEvent event){
System.out.println(Thread.currentThread().getName()+"-"+event.getSource()+"===="+event.getTimestamp());
if(event.getSource().equals("person-sayhello")){
int num=1/0;
}
} @Override
public int getOrder() {
return 0;
} @Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType){
if(PayloadApplicationEvent.class.isAssignableFrom(eventType)){
System.out.println("非ApplicationEvent的广播");
return true;
}else if(MyApplicationEvent.class.isAssignableFrom(eventType)) {
System.out.println("自定义广播");
return true;
}
return false; } @Override
public boolean supportsSourceType(Class<?> sourceType) {
System.out.println("sourceType====="+sourceType);
return true;
} }
4、bean内容,因为在bean中发出广播,需要使用context,此处直接使用ApplicationContextAware接口形式注入bean
public class Person implements ApplicationContextAware {
private ApplicationContext applicationContext;
private String name;
public boolean running; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public void sayHello(){
applicationContext.publishEvent(new MyApplicationEvent("person-sayhello"));
System.out.println("hello "+this.name);
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
5、测试程序
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
context.publishEvent("init-bean");
Person person=(Person)context.getBean("person");
person.sayHello();
}
}
6、运行结果我们可以看到不同的广播按照规则进行了过滤,而且最终是使用不同的线程进行发送。如果有异常,errorHandler顺利捕捉到了异常。那么这个例子基本涵盖了广播的所有点。
非ApplicationEvent的广播 //非系统广播自定义广播可以通过
sourceType=====class org.springframework.context.support.ClassPathXmlApplicationContext //来自context的广播可以通过
自定义广播 //自定义广播可以通过
sourceType=====class java.lang.String //来自主程序的广播可以通过
hello zhangsan
pool-1-thread-2-person-sayhello====1462862846700 //非event广播内容
捕获到了异常:/ by zero//异常
pool-1-thread-1-org.springframework.context.support.ClassPathXmlApplicationContext@48503868: startup date [Tue May 10 14:47:25 CST 2016]; root of context hierarchy====1462862846699 //自定义广播
[spring源码学习]九、IOC源码-applicationEventMulticaster事件广播的更多相关文章
- Spring Ioc源码分析系列--Ioc源码入口分析
Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...
- spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign
基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(一)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(二)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- go 源码学习之---Tail 源码分析
已经有两个月没有写博客了,也有好几个月没有看go相关的内容了,由于工作原因最近在做java以及大数据相关的内容,导致最近工作较忙,博客停止了更新,正好想捡起之前go的东西,所以找了一个源码学习 这个也 ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(三)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- Jetty源码学习-编译Jetty源码二三事
工作小几个月了,JDK基础和web应用框架学的的差不多了,开始学习Jetty源码,费了小半天才编译成功,把自己拆过的坑记录下来. 编译前的环境: MAVEN 3.3.Eclips eLuna Serv ...
- Spring源码学习之IOC容器实现原理(一)-DefaultListableBeanFactory
从这个继承体系结构图来看,我们可以发现DefaultListableBeanFactory是第一个非抽象类,非接口类.实际IOC容器.所以这篇博客以DefaultListableBeanFactory ...
随机推荐
- 【Java】增强的for流程
增强for循环语法: for(type element: array) { System.out.println(element); } 可遍历输出数组元素,但无法获取元素下标. 相关链接 ...
- DOM0,DOM2,DOM3 事件基础知识
事件是javascript和HTML交互基础, 任何文档或者浏览器窗口发生的交互, 都要通过绑定事件进行交互; 事件有DOM0, DOM2和DOM3的区分(别问我怎么少了一个DOM1, 也没找到DOM ...
- Java 中文乱码问题总结
开发java应用出现乱码是很常见的,毕竟现在unicode的使用还不是很广泛,在使用gb2312(包含了gbk简体,big5繁体)的系统中要正确 实现中文的display和数据库的存储是最基本的要求. ...
- [转]MySQL索引背后的数据结构及算法原理
摘要 本文以MySQL数据库为研究对象,讨论与数据库索引相关的一些话题.特别需要说明的是,MySQL支持诸多存储引擎,而各种存储引擎对索引的支持也各不相同,因此MySQL数据库支持多种索引类型,如BT ...
- file xxx from install of xxx conflicts with file from xxx
执行安装 rpm -ivh lib64stdc++6-4.6.1-2-mdv2011.0.x86_64.rpm 时提示以下错误: warning: lib64stdc++6-4.6.1-2-mdv20 ...
- Javascript设计模式学习三(策略模式)
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以互相替换.目的:将算法的使用和算法的实现分离开来.比如: if(input == 'A'){ return 1; } if(input == ...
- 负载均衡-基础-一致性哈希算法及java实现
一致性hash算法,参考: http://www.blogjava.net/hello-yun/archive/2012/10/10/389289.html 针对这篇文章,加入了自己的理解,在原有的代 ...
- PHPSTORM设置新建PHP文件头部注释
1.选择 File|setting 2.在Editor|File and Code Templates,见下图标记更改即可
- 关于更新发布CSS和JS文件的缓存问题
现如今,浏览器大战下,各个浏览器也是拼了命的提高性能,升级JS解析引擎,更好的处理浏览器的页面缓存,让用户的浏览体验更快,占用更小的PC资源.那么,问题就出现在JS和CSS缓存,甚至页面缓存上.至于浏 ...
- CSS3 media 入门
css3 media 严格来说是自适应布局 对不同的屏幕(正确的说应该是) 写不同的css样式.而流式布局 则才算是响应式布局. css3 media 语法: @media mediatype a ...