EventBus源码学习笔记(一)
EventBus 深入学习一
EventBus是一个消息总线,以观察者模式实现,用于简化程序的组件、线程通信,可以轻易切换线程、开辟线程; 传统上,Java的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的。设计EventBus就是为了取代这种显示注册方式,使组件间有了更好的解耦。EventBus不是通用型的发布-订阅实现,不适用于进程间通信
序
开始之前,我们可以先想一下,什么东西是发布-订阅模型,如果要让我们自己设计一个发布-订阅模型的框架,要怎么处理
举一个小例子,谈一下我的理解
有一个存钱罐,长辈向里面放钱(1毛,5毛,1元,5元,10元,20元,50元,100元), 晚辈从里面取钱;与现实有点不同的是,每个长辈只能投相同面额的钱,晚辈只能获取相同面额的钱 假设小红,小明,小刚三个人是取10元钱的,现在张三放了10元钱, 然后主动把这10元钱给这三个人,不管他们去干嘛(与现实不同的是,张三只防了10元钱,但是小红,小明,小刚都拿到了这10元钱)
接本的业务流程如下:
- 三个角色,发布消息者(长辈),存储消息的管道(存钱罐子),订阅者(晚辈);
- 发布者将发送消息到消息管道
- 管道则将消息推送给订阅者
设计:
- 发布者: 任何发布消息的人
- 消息管道: 连接发布者和订阅者的桥梁,主要有两个功能,一是接受发布者发布的消息;而是将消息推送给订阅者
- 维护一个订阅者关系(消息->订阅者), 因此需要开放给订阅者一个注册的接口
- 接受发布者发送消息, 因此需要开放给发布者一个发布消息的接口
- 推送消息给订阅者, (这里调用订阅者提供的消息接受回调方法,实现消息推送)
- 订阅者: 每个订阅者,关注并处理一种消息类型,
- 想成为一个订阅者,先到消息管道这里注册,(告知消费信息的类型以及接受消息的回调方法)
- 接受消息的回调方法(即接受消息后执行业务逻辑的主体)
使用
使用非常简单, 创建一个 EventBus
实例, 订阅方,调用 EventBus.register()
方法注册, 消息发布方,调用eventBus.post(event);
来发布消息, 则订阅类中,添加 @subscribe 注解的方法将会接受到这条消息
实例如下:
/**
* 发送的消息
*/
@ToString
@Getter
@Setter
public class AuditEvent {
/**
* 审核人
*/
private String auditUser;
/**
* 审核记录
*/
private String record;
/**
* 审核结果
*/
private AuditEventEnum auditResultEnum;
public enum AuditEventEnum {
ACCEPT,
REJECT,
RETRY;
}
}
订阅者
/**
* 初审 & 复审的监听器
* <p/>
* Created by yihui on 2017/3/1.
*/
@Component
public class AuditEventListener {
private static final Logger logger = LoggerFactory.getLogger(AuditEventListener.class);
/**
* 注册事件
*/
@PostConstruct
public void init() {
ActionEventBus.register(this);
}
/**
* 审核完成后,会发送一条消息
* <p/>
* 1. 通过审核
* 2. 拒绝审核
* 3. 重新审核
* <p/>
* 根据消息, 做出不同的action
*
* @param args
*/
@Subscribe
public void invoke(AuditEvent args) {
if (args.getAuditResultEnum() == AuditEvent.AuditEventEnum.ACCEPT) {
System.out.println(1111);
logger.info("审核通过!!!! {}", args.getRecord());
} else if (args.getAuditResultEnum() == AuditEvent.AuditEventEnum.REJECT) {
System.out.println(2222);
logger.info("审核拒绝!!!! {}", args.getRecord());
} else {
logger.info("重新审核!!!! {}", args.getRecord());
}
}
}
EventBus管理工厂
public class ActionEventBus {
private final static EventBus eventBus = new EventBus();
public static void post(Object event) {
eventBus.post(event);
}
public static void register(Object handler) {
eventBus.register(handler);
}
public static void unregister(Object handler) {
eventBus.unregister(handler);
}
}
发布消息
@Test
public void testAudit() {
AuditEvent auditEvent = new AuditEvent();
auditEvent.setAuditResultEnum(AuditEvent.AuditEventEnum.ACCEPT);
auditEvent.setAuditUser("1hui");
auditEvent.setRecord("test1");
// 发布一条成功的消息
ActionEventBus.post(auditEvent);
auditEvent.setAuditResultEnum(AuditEvent.AuditEventEnum.REJECT);
auditEvent.setAuditUser("2hui");
auditEvent.setRecord("test2");
// 发布一条拒绝的消息
ActionEventBus.post(auditEvent);
BuyEvent buyEvent = new BuyEvent();
buyEvent.setBuyerUser("3hui");
buyEvent.setCount(1);
buyEvent.setTotalPrice(10000L);
buyEvent.setItem("java book");
buyEvent.setBuyEventEnum(BuyEventEnum.PAY);
ActionEventBus.post(buyEvent);
System.out.println("over");
}
分析
说明,我们先拿google的 Guava中的 EventBus 来作为研究对象; 后续会对比下android平台上使用非常多的
greenrobot/EventBus
从上面的使用可以简单的看出EventBus
的设计思路基本上还是 消息-订阅的模子,但是设计非常巧妙
从订阅者角度来看,首先是要注册,没什么好说的,关键点就在于接受消息的处理方法上
- 添加一个注解,指定消息接收类型(即参数类型), 就可以接受这类消息
- 基于上面的方法,一个订阅者,可以实现订阅多个不同的消息源
消息发布方来看,直接调用 EventBus.post()
就算是发布消息,使用起来超级简单
其中 EventBus
作为沟通的桥梁,也就是上面我们说的‘储钱罐’, 如果希冀实现异步的消息处理,则直接用AsyncEventBus
即可
从上面的使用来看,极大的简化了使用的流程,简直不能更easy
了; 唯一的遗憾是,从上面的描述中,发现使用异步的话,还得改用AsyncEventBus
有点麻烦,如果能直接再 @subscribe
注解中添加个标识,表示是否使用异步消费就完美了
前期准备
在真正进入源码分析之前,我们先做些准备工作,了解下基本的术语和背景
1. 事件监听者(Listeners)
即我们上面的订阅者,最终接受事件,并执行响应的业务逻辑的主体
在EventBus实例上调用EventBus.register(Object)
方法注册事件监听者;需要注意的是请保证事件生产者和监听者共享相同的EventBus实例
2. 事件生产者(Producers)
发送事件的主体,通过把事件传递给 EventBus.post(Object)
方法。异步分发可以直接用EventBus的子类AsyncEventBus
。
3. 术语
术语 |
说明 |
---|---|
事件 |
可以向事件总线发布的对象 |
订阅 |
向事件总线注册监听者以接受事件的行为 |
监听者 |
提供一个处理方法,希望接受和处理事件的对象 |
处理方法 |
监听者提供的公共方法,事件总线使用该方法向监听者发送事件;该方法应该用Subscribe注解 |
发布消息 |
通过事件总线向所有匹配的监听者提供事件 |
- 深入探索Scala的Option
- 在Clion的IDE中指定命令行参数
- CentOS6 安装并破解Jira 7
- Martin Odersky访谈录所思
- 解决Boost库链接出错问题
- 引入Option优雅地保证健壮性
- java正则校验,密码必须由字母和数字组成
- Spring Boot集成JasperReports生成PDF文档
- Redux框架reducer对状态的处理
- 使用Spring Cloud Security OAuth2搭建授权服务
- Nginx性能优化
- linux 如何正确的关闭mongodb
- 运用Aggregator模式实现MapReduce
- vue 2 使用Bus.js进行兄弟(非父子)组件通信 简单案例
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 百度黄埔学院:十行代码高效完成深度学习POC
- 【数据相关】目标检测中的数据标注及格式转换代码
- 科学与艺术的融合:遗传算法绘制蒙娜丽莎
- “工业听诊”中多声源事件检测与定位
- 工业党福利:使用PaddleX高效实现指针型表计读取系列文章(2)
- 【三维点云系列】PCL点云库之数据文件与IO操作
- Jvm故障处理工具
- 递增子序列
- redis学习(二)
- You-Get 使用方法
- 接口测试 Mock 实战 | 结合 jq 完成批量化的手工 Mock
- 在Angular应用的child Component里同时使用@Input和@Output
- Angular应用里的@Input和@Output注解使用方法介绍
- K8S Ingress使用|常见问题列表
- 部署Tomcat及负载均衡