EventBus 类解析

当我们开发软件时,各个对象之间的数据共享和合作是必须的。 但是这里比较难做的是 怎样保证消息之间的传输高效并且减少各个模块之间的耦合。 当组件的职责不清楚时,一个组件还要承担另一个组件的职责,这样的系统我们就认为是高耦合。 当我们的系统变得高耦合时,任何一个小的改动都会对系统造成影响。 为了解决设计上的问题,我们设计了基于事件的设计模型。 在事件驱动编程模型中,对象可以发布/订阅 事件. 事件监听者就是监听事件的发生,我们在第六章中已经看到过RemovalListener, 在这一章中,我们将讨论Guava的EventBus类,了解它的发布/订阅事件是怎么使用的。

这一章,我们将覆盖下面的知识点:
-- EventBus 和 AsyncEventBus类
-- 怎样是用EventBus订阅事件
-- 使用EventBus发布事件
-- 编写事件处理器,并且根据我们的需求选择合适的处理器
-- 与DI工具协作

EventBus

EventBus类是guava中关注消息的发布和订阅的类,简单的说订阅者通过EventBus注册并订阅事件,发布者将事件发送到EventBus中,EventBus将事件顺序的通知给时间订阅者,所以 这里面有一个重要的注意点,事件处理器必须迅速的处理,否则可能会导致时间堆积。

创建EventBus实例

创建一个EventBus实例,只需要简单的调用构造方法:

EventBus eventBus = new EventBus();

也提供了一个带参数的构造类,目的只是为了加上一个标识:

EventBus eventBus = new EventBus(TradeAccountEvent.class.getName());

订阅事件

为了接受到一个事件,我们需要做下面3个步骤:

  1. 这个类需要定义一个只接受一个参数的public方法, 参数的类型要和订阅的事件类型一只。
  2. 需要在方法上加上@Subscribe注解
  3. 最后我们调用EventBus的register方法注册对象

发布事件

我们可以调用EventBus.post方法发送事件,EventBus会轮流调用所有的接受类型是发送事件类型的订阅者,但是这里面有一个比较强大的东西,就是。。。。。。。。。。。

定义事件处理方法

如前面提到了事件处理方法只能接受一个参数,EventBus会轮流顺序调用订阅的方法,因此事件处理方法必须很快的给予响应,如果说时间处理的方法中有需要进行长时间运算的过程,我们建议另起一个线程处理。

并发

EventBus不会起多个线程去调用时间处理方法,除非我们在事件的处理方法上加上注解@AllowCOncurrentEvent,加上这个注解后我们就会认为这个事件处理方法是线程安全的.Annotating a handler method with the @
AllowConcurrentEvent annotation by itself will not register a method with EventBus

现在我们来看看怎么使用EventBus,我们来看一些例子.

订阅事件

我们假设我们已经像如下的方式定义了一个事件:

public class TradeAccountEvent {
private double amount;
private Date tradeExecutionTime;
private TradeType tradeType;
private TradeAccount tradeAccount;
public TradeAccountEvent(TradeAccount account, double amount,
Date tradeExecutionTime, TradeType tradeType) {
checkArgument(amount > 0.0, "Trade can't be less than
zero");
this.amount = amount;
this.tradeExecutionTime =
checkNotNull(tradeExecutionTime,"ExecutionTime can't be null");
this.tradeAccount = checkNotNull(account,"Account can't be
null");
this.tradeType = checkNotNull(tradeType,"TradeType can't
be null");
}

由上面可以看出,无论是买或者卖的事件发生,我们都会创建一个TradeAccountEvent对象,现在让我们考虑一下我们当这个事件被执行时我们希望监听者能够接收到,我们定义SimpleTradeAuditor类:

public class SimpleTradeAuditor {
private List<TradeAccountEvent> tradeEvents =
Lists.newArrayList();
public SimpleTradeAuditor(EventBus eventBus){
eventBus.register(this);
}
@Subscribe
public void auditTrade(TradeAccountEvent tradeAccountEvent){
tradeEvents.add(tradeAccountEvent);
System.out.println("Received trade "+tradeAccountEvent);
}
}

我们可以快速的看一下代码,在构造方法中,我们接受一个EventBus实例,接着我们注册SimpleTradeAuditor类到EventBus中,接受事件TradeAccountEvents. 通过指定@Subscribe注解说明哪个方法是事件处理器. 上面例子中的处理方式:将event加入到list中,并且在控制台中打印出来.

事件发布 例子

现在我们看一下怎样发布一个事件,看下面的类:

public class SimpleTradeExecutor {
private EventBus eventBus;
public SimpleTradeExecutor(EventBus eventBus) {
this.eventBus = eventBus;
}
public void executeTrade(TradeAccount tradeAccount, double
amount, TradeType tradeType){
TradeAccountEvent tradeAccountEvent =
processTrade(tradeAccount, amount, tradeType);
eventBus.post(tradeAccountEvent);
}
private TradeAccountEvent processTrade(TradeAccount
tradeAccount, double amount, TradeType tradeType){
Date executionTime = new Date();
String message = String.format("Processed trade for %s of
amount %n type %s @
%s",tradeAccount,amount,tradeType,executionTime);
TradeAccountEvent tradeAccountEvent = new TradeAccountEvent(tr
adeAccount,amount,executionTime,tradeType);
System.out.println(message);
return tradeAccountEvent;
}
}

像上面的SimpleTradeAuditor类一样,在SimpleTradeExecutor的构造方法中我们也接受一个EventBus作为构造参数. 和 SimpleTradeAuditor类,为了方便后面使用,我们使用了一个成员变量引用了eventbus类,尽管大多数情况下,在两个类中使用同一个eventBus实例是不好的,我们将在后面的例子中去看怎样使用多个EventBus实例。 但是在这个例子中,我们使用同一个实例. SimpleTradeExecutor类有一个公开的方法,executeTrade接受我们处理一个trade的所有信息。 在这个例子中我们调用processTrade方法,传入了必要的信心并且在控制台中打印了交易已经被执行,并且返回一个TradeAccountEvent实例。 当processTrade 方法执行完,我们调用EventBus的post方法将TradeAccountEvent作为参数, 这样所有订阅TradeAccountEvent事件的订阅者都会收到这个消息。 这样我们就可以看到,publish 类和 scribe类通过消息解耦了

精确订阅

我们刚才了解了怎样使用EventBus订阅发布事件。 我们知道 EventBus事件的发布与订阅是基于事件类型的, 这样我们就可以通过事件类型将事件发送给不同的订阅者。 比如: 如果我们我们想分别订阅 买和卖事件。 首先我们创建两种类型的事件:

public class SellEvent extends TradeAccountEvent {
public SellEvent(TradeAccount tradeAccount, double amount, Date
tradExecutionTime) {
super(tradeAccount, amount, tradExecutionTime, TradeType.
SELL);
}
}
public class BuyEvent extends TradeAccountEvent {
public BuyEvent(TradeAccount tradeAccount, double amount, Date
tradExecutionTime) {
super(tradeAccount, amount, tradExecutionTime, TradeType.BUY);
}
}

现在我们创建了两种不同类型的事件实例,SellEvent和BuyEvent,两个事件都继承了TradeAccountEvent。 我们能够实现分别的订阅,我们先创建一个能够订阅SellEvent的实例:

public class TradeSellAuditor {
private List<SellEvent> sellEvents = Lists.newArrayList();
public TradeSellAuditor(EventBus eventBus) {
eventBus.register(this);
}
@Subscribe
public void auditSell(SellEvent sellEvent){
sellEvents.add(sellEvent);
System.out.println("Received SellEvent "+sellEvent);
}
public List<SellEvent> getSellEvents() {
return sellEvents;
}
}

从功能点上来看,这个实例和我们之前的SimpleTradeAuditor差不多,只不过上面的这个实例只接受SellEvent事件,下面我们再创建一个只接受BuyEvent事件的实例:

public class TradeBuyAuditor {
private List<BuyEvent> buyEvents = Lists.newArrayList();
public TradeBuyAuditor(EventBus eventBus) {
eventBus.register(this);
}
@Subscribe
public void auditBuy(BuyEvent buyEvent){
buyEvents.add(buyEvent);
System.out.println("Received TradeBuyEvent "+buyEvent);
}
public List<BuyEvent> getBuyEvents() {
return buyEvents;
}
}

现在我们只需要重构我们的SimpleTradeExecutor类去基于buy或者sell创建正确的TradeAccountEvent。

public class BuySellTradeExecutor {
… deatails left out for clarity same as SimpleTradeExecutor
//The executeTrade() method is unchanged from SimpleTradeExecutor
private TradeAccountEvent processTrade(TradeAccount tradeAccount,
double amount, TradeType tradeType) {
Date executionTime = new Date();
String message = String.format("Processed trade for %s of
amount %n type %s @ %s", tradeAccount, amount, tradeType,
executionTime);
TradeAccountEvent tradeAccountEvent;
if (tradeType.equals(TradeType.BUY)) {
tradeAccountEvent = new BuyEvent(tradeAccount, amount,
executionTime);
} else {
tradeAccountEvent = new SellEvent(tradeAccount,
amount, executionTime);
}
System.out.println(message);
return tradeAccountEvent;
}
}

这里我们创建了和SimpleTradeExecutor功能相似的类:BuySellTradeExecutor,只不过BuySellTradeExecutor根据交易类型创建了不同的事件,BuyEvent和SellEvent。 我们发布了不同的事件,注册了不通的订阅者,但是EventBus对这样的改变没有感知。 为了接受这两个事件,我们不需要创建两个类,我们只需要像如下这种方式就可以:

public class AllTradesAuditor {
private List<BuyEvent> buyEvents = Lists.newArrayList();
private List<SellEvent> sellEvents = Lists.newArrayList();public AllTradesAuditor(EventBus eventBus) {
eventBus.register(this);
}
@Subscribe
public void auditSell(SellEvent sellEvent){
sellEvents.add(sellEvent);
System.out.println("Received TradeSellEvent "+sellEvent);
}
@Subscribe
public void auditBuy(BuyEvent buyEvent){
buyEvents.add(buyEvent);
System.out.println("Received TradeBuyEvent "+buyEvent);
}
}

上面我们创建一个实例,有两个事件处理方法,这样AllTradeAuditor会接受所有的Trade事件。 哪个方法被调用取决于EventBus发送什么类型的事件。 为了验证一下,我们可以写一个方法接受Object类型的参数,这样就可以收到所有的事件。
下面我们来考虑一下我们有多个EventBus实例。 如果我们把BuySellTradeExecutor类拆分成两个类,这样我们就可以注入两个不同的EventBus实例,但是在订阅类中就要注意注入的是哪个类了。 关于这个例子我们在这里不讨论了,代码可以见

bbejeck.guava.chapter7.config 包。

取消事件订阅

我们订阅事件的时候,肯定也会想到在某个时间点我们需要取消订阅某个事件。 取消事件订阅只需要调用eventBus.unregister方法。 如果我们知道我们在某个时刻想要停止对某个事件的处理,我们可以按照如下的方式处理:

public void unregister(){
this.eventBus.unregister(this);
}

一旦上面的方法被调用,就不在会收到任何事件,其他的没有取消订阅的会继续收到事件.

异步事件总线

我们之前一直强调事件处理器的处理逻辑要简单。 因为EventBus是顺序的处理每一个事件的。 我们还有另外一种处理方式: AsyncEventBus. AsyncEventBus提供了和EventBus相同的功能。只是在处理事件的时候采用了Java.util.concurrent.Executor 调用事件处理器。

创建一个异步EventBus实例

创建AsyncEvent和创建一个EventBus差不多:

AsyncEventBus asyncEventBus = new AsyncEventBus(executorService);

我们创建一个传入ExecutorService实例的AsyncEvent实例。 我们还有一个接受两个参数的构造函数,接受另外一个string表明ExecutorService的身份。 AysncEventBus在事件处理器需要花费时间比较长的场景下比较适合。

DeadEvent

当一个事件没有监听者,我们就会将这样的事件包装成DeadEvent,这样有一个方法监听DeadEvent我们就可以知道哪些事件没有监听者。

public class DeadEventSubscriber {
private static final Logger logger =
Logger.getLogger(DeadEventSubscriber.class);
public DeadEventSubscriber(EventBus eventBus) {
eventBus.register(this);
}
@Subscribe
public void handleUnsubscribedEvent(DeadEvent deadEvent){
logger.warn("No subscribers for "+deadEvent.getEvent());
}
}

上面的例子中我们简单的注册了一个监听DeadEvent的监听者,简单的记录了没有被监听的事件。

依赖注入

为了确保我们注册的监听者和发布者是同一个EventBus实例,我们使用Spring来实现EventBus的注入。 在下面的例子中,我们将展示怎样使用Spring框架去配置SimpleTradeAuditor 和 SimpleTradeExecutor类。首先我们需要对SimpleTradeAuditor和SimpleTradeExecutor类做如下的改变:

@Component
public class SimpleTradeExecutor {
private EventBus eventBus;
@Autowired
public SimpleTradeExecutor(EventBus eventBus) {
this.eventBus = checkNotNull(eventBus, "EventBus can't be
null");
}@Component
public class SimpleTradeAuditor {
private List<TradeAccountEvent> tradeEvents =
Lists.newArrayList();
@Autowired
public SimpleTradeAuditor(EventBus eventBus){
checkNotNull(eventBus,"EventBus can't be null");
eventBus.register(this);
}

我们首先在两个类上加上了@Component注解。 这样可以让Spring把这两个类当作可以注入的bean。这里我们使用的构造方法注入。所以我们加上了@Autowired注解。 加上了@Autowired注解spring就可以帮助我们注入EventBus类。

@Configuration
@ComponentScan(basePackages = {"bbejeck.guava.chapter7.publisher",
"bbejeck.guava.chapter7.subscriber"})
public class EventBusConfig {
@Bean
public EventBus eventBus() {
return new EventBus();
}
}

这里我们的类上加上了@Configuration注解,这样spring就把这个类当作Context,在这个类中我们返回了EventBus实例。这样spring就对上面的两个类注入了同一个EventBus,这正是我们想要的,至于spring是怎么做到的,不在本书考虑的范围。

总结

在这一章,我们讲解了怎样使用Guava进行事件驱动编程,来降低模块之间的耦合,我们讲解了怎么创建EventBus实例,并且怎样注册监听者和发布者。 并且我们也剖析了怎样更具事件类型去监听事件。 最后我们还学习了使用AsyncEventBus类,可以让我们异步的发送事件。我们也学习了怎样使用DeadEvent类去确保我们监听了所有的事件。 最后,我们还学习了使用依赖注入来使得我们可以更容易创建基于事件的系统。

Guava翻译系列之EventBus相关推荐

  1. guava翻译系列之Collections

    引言 集合对于任何一门语言都是必须的.没有集合我们写不出一些复杂的逻辑.Guava继承扩展了Google Collections的一些功能. 从 com.google.common.collect包下 ...

  2. CS231n课程笔记翻译系列之目录汇总

    知乎上CS231n课程翻译系列 翻译的笔记非常好,为了方便查看,这里把所有目录列于此,并给出链接. Python Numpy教程(全篇) Python 基本数据类型 容器(列表, 字典, 集合, 元组 ...

  3. 博文翻译系列——如何入门数据科学 without spending a penny

    转载请注明出处:https://blog.csdn.net/u011995719/article/details/120899558 博文翻译转载系列--基于"输入输出"学习方法, ...

  4. TypeScript手册翻译系列4-模块

    为什么80%的码农都做不了架构师?>>>    模块 在TypeScript中利用模块(module)来组织代码.这里将讨论内部和外部模块,以及在何时使用哪种方式更合适,以及怎么使用 ...

  5. Highcharts翻译系列三:exporting导出和打印选项

    Highcharts翻译系列三:exporting导出和打印选项 exporting 导出和打印选项 参数 描述 默认值 buttons exportButton.printButton 导出和打印的 ...

  6. Highcharts翻译系列十二:gauge测量图

    Highcharts翻译系列十二:gauge测量图 说明 测量图需要highcharts-more.js的支持 属性 参数 描述 默认值 animation 动画 true color 主要颜色或序列 ...

  7. 《Qt for Symbian》翻译系列之七:第二章 开始(1)

    <Qt for Symbian>翻译系列之七:第二章 开始(1) 2011年04月06日 本章主要介绍应用于Symbian平台的QT开发工具.对于Symbian平台的新手,本章首先通过逐步 ...

  8. Knockoutjs官网翻译系列(一)

    最近马上要开始一个新项目的研发,作为第一次mvvm应用的尝试,我决定使用knockoutjs框架.作为学习的开始就从官网的Document翻译开始吧,这样会增加印象并加入自己的思考,说是翻译也并不是纯 ...

  9. Guava学习笔记:EventBus(转)

    EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现.对于事件监听和发布订阅模式,EventBus是一个非常优雅和简单解决方案,我们不用创建复杂的类和 ...

最新文章

  1. MYSQL的字符序_mysql字符序核心概念
  2. python写自动答题脚本_问卷星的自动答题脚本
  3. 435. 无重叠区间(贪心算法)
  4. Npoi导出excel整理(附源码)
  5. 移动端隐藏滚动条(最全面)
  6. 释放数据价值:DAYU数据运营新能力解读
  7. QFile和C语言对文件操作的性能比较.--读取double型二进制数据文件
  8. python class类的self_Python类class参数self原理解析
  9. NUC1015 计算数字的根
  10. 使用seaborn制图(箱型图)
  11. mouseenter 事件
  12. UltraCompare无限30天试用的方法
  13. python 连通区域_opencv 查找连通区域 最大面积实例
  14. All-one Matrices
  15. 回归中的相关度和决定系数
  16. 块元素盒子内容被撑开
  17. CAD制图初学入门:CAD机械软件中如何构造孔?
  18. 爱快最新版3.6用docker安装Jellyfin最新教程
  19. python中break语句的用法_Python break语句用法示例
  20. 2020 ICPC沈阳站-D,H

热门文章

  1. 计算机培训开场语,辅导班家长会主持词开场白
  2. 服务器操作系统与安装步骤,服务器操作系统与安装步骤
  3. d3.js html显示图片,d3.js v4:如何在鼠标点击节点后显示图像
  4. 栈windows linux,Linux+Windows: 程序崩溃时,在 C++ 代码中,如何获取函数调用栈信息...
  5. vue仿饿了么点餐手机端
  6. ci mysql pdo_CI框架中pdo的使用方法
  7. c语言不会可以学好java吗_有人说学了C语言,两天就能学会Java,两个星期就可以找工作?...
  8. linux13位时间戳,Kotlin 处理Linux时间戳
  9. 2020.2idea怎么创建html项目_陈肆横项目日记:百度百科怎么创建自己的名字
  10. 华为升级harmonyos的机型名单,华为鸿蒙 OS 2.0 系统适配名单已出,四月推送,天玑机型暂时无缘...