Guava翻译系列之EventBus
EventBus 類解析
當(dāng)我們開發(fā)軟件時(shí),各個(gè)對(duì)象之間的數(shù)據(jù)共享和合作是必須的。 但是這里比較難做的是 怎樣保證消息之間的傳輸高效并且減少各個(gè)模塊之間的耦合。 當(dāng)組件的職責(zé)不清楚時(shí),一個(gè)組件還要承擔(dān)另一個(gè)組件的職責(zé),這樣的系統(tǒng)我們就認(rèn)為是高耦合。 當(dāng)我們的系統(tǒng)變得高耦合時(shí),任何一個(gè)小的改動(dòng)都會(huì)對(duì)系統(tǒng)造成影響。 為了解決設(shè)計(jì)上的問題,我們?cè)O(shè)計(jì)了基于事件的設(shè)計(jì)模型。 在事件驅(qū)動(dòng)編程模型中,對(duì)象可以發(fā)布/訂閱 事件. 事件監(jiān)聽者就是監(jiān)聽事件的發(fā)生,我們?cè)诘诹轮幸呀?jīng)看到過RemovalListener, 在這一章中,我們將討論Guava的EventBus類,了解它的發(fā)布/訂閱事件是怎么使用的。
這一章,我們將覆蓋下面的知識(shí)點(diǎn):
-- EventBus 和 AsyncEventBus類
-- 怎樣是用EventBus訂閱事件
-- 使用EventBus發(fā)布事件
-- 編寫事件處理器,并且根據(jù)我們的需求選擇合適的處理器
-- 與DI工具協(xié)作
EventBus
EventBus類是guava中關(guān)注消息的發(fā)布和訂閱的類,簡(jiǎn)單的說訂閱者通過EventBus注冊(cè)并訂閱事件,發(fā)布者將事件發(fā)送到EventBus中,EventBus將事件順序的通知給時(shí)間訂閱者,所以 這里面有一個(gè)重要的注意點(diǎn),事件處理器必須迅速的處理,否則可能會(huì)導(dǎo)致時(shí)間堆積。
創(chuàng)建EventBus實(shí)例
創(chuàng)建一個(gè)EventBus實(shí)例,只需要簡(jiǎn)單的調(diào)用構(gòu)造方法:
EventBus eventBus = new EventBus();也提供了一個(gè)帶參數(shù)的構(gòu)造類,目的只是為了加上一個(gè)標(biāo)識(shí):
EventBus eventBus = new EventBus(TradeAccountEvent.class.getName());訂閱事件
為了接受到一個(gè)事件,我們需要做下面3個(gè)步驟:
發(fā)布事件
我們可以調(diào)用EventBus.post方法發(fā)送事件,EventBus會(huì)輪流調(diào)用所有的接受類型是發(fā)送事件類型的訂閱者,但是這里面有一個(gè)比較強(qiáng)大的東西,就是。。。。。。。。。。。
定義事件處理方法
如前面提到了事件處理方法只能接受一個(gè)參數(shù),EventBus會(huì)輪流順序調(diào)用訂閱的方法,因此事件處理方法必須很快的給予響應(yīng),如果說時(shí)間處理的方法中有需要進(jìn)行長(zhǎng)時(shí)間運(yùn)算的過程,我們建議另起一個(gè)線程處理。
并發(fā)
EventBus不會(huì)起多個(gè)線程去調(diào)用時(shí)間處理方法,除非我們?cè)谑录奶幚矸椒ㄉ霞由献⒔?#64;AllowCOncurrentEvent,加上這個(gè)注解后我們就會(huì)認(rèn)為這個(gè)事件處理方法是線程安全的.Annotating a handler method with the @
AllowConcurrentEvent annotation by itself will not register a method with EventBus
現(xiàn)在我們來看看怎么使用EventBus,我們來看一些例子.
訂閱事件
我們假設(shè)我們已經(jīng)像如下的方式定義了一個(gè)事件:
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"); }由上面可以看出,無論是買或者賣的事件發(fā)生,我們都會(huì)創(chuàng)建一個(gè)TradeAccountEvent對(duì)象,現(xiàn)在讓我們考慮一下我們當(dāng)這個(gè)事件被執(zhí)行時(shí)我們希望監(jiān)聽者能夠接收到,我們定義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); } }我們可以快速的看一下代碼,在構(gòu)造方法中,我們接受一個(gè)EventBus實(shí)例,接著我們注冊(cè)SimpleTradeAuditor類到EventBus中,接受事件TradeAccountEvents. 通過指定@Subscribe注解說明哪個(gè)方法是事件處理器. 上面例子中的處理方式:將event加入到list中,并且在控制臺(tái)中打印出來.
事件發(fā)布 例子
現(xiàn)在我們看一下怎樣發(fā)布一個(gè)事件,看下面的類:
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的構(gòu)造方法中我們也接受一個(gè)EventBus作為構(gòu)造參數(shù). 和 SimpleTradeAuditor類,為了方便后面使用,我們使用了一個(gè)成員變量引用了eventbus類,盡管大多數(shù)情況下,在兩個(gè)類中使用同一個(gè)eventBus實(shí)例是不好的,我們將在后面的例子中去看怎樣使用多個(gè)EventBus實(shí)例。 但是在這個(gè)例子中,我們使用同一個(gè)實(shí)例. SimpleTradeExecutor類有一個(gè)公開的方法,executeTrade接受我們處理一個(gè)trade的所有信息。 在這個(gè)例子中我們調(diào)用processTrade方法,傳入了必要的信心并且在控制臺(tái)中打印了交易已經(jīng)被執(zhí)行,并且返回一個(gè)TradeAccountEvent實(shí)例。 當(dāng)processTrade 方法執(zhí)行完,我們調(diào)用EventBus的post方法將TradeAccountEvent作為參數(shù), 這樣所有訂閱TradeAccountEvent事件的訂閱者都會(huì)收到這個(gè)消息。 這樣我們就可以看到,publish 類和 scribe類通過消息解耦了
精確訂閱
我們剛才了解了怎樣使用EventBus訂閱發(fā)布事件。 我們知道 EventBus事件的發(fā)布與訂閱是基于事件類型的, 這樣我們就可以通過事件類型將事件發(fā)送給不同的訂閱者。 比如: 如果我們我們想分別訂閱 買和賣事件。 首先我們創(chuàng)建兩種類型的事件:
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); } }現(xiàn)在我們創(chuàng)建了兩種不同類型的事件實(shí)例,SellEvent和BuyEvent,兩個(gè)事件都繼承了TradeAccountEvent。 我們能夠?qū)崿F(xiàn)分別的訂閱,我們先創(chuàng)建一個(gè)能夠訂閱SellEvent的實(shí)例:
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; } }從功能點(diǎn)上來看,這個(gè)實(shí)例和我們之前的SimpleTradeAuditor差不多,只不過上面的這個(gè)實(shí)例只接受SellEvent事件,下面我們?cè)賱?chuàng)建一個(gè)只接受BuyEvent事件的實(shí)例:
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; } }現(xiàn)在我們只需要重構(gòu)我們的SimpleTradeExecutor類去基于buy或者sell創(chuàng)建正確的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; } }這里我們創(chuàng)建了和SimpleTradeExecutor功能相似的類:BuySellTradeExecutor,只不過BuySellTradeExecutor根據(jù)交易類型創(chuàng)建了不同的事件,BuyEvent和SellEvent。 我們發(fā)布了不同的事件,注冊(cè)了不通的訂閱者,但是EventBus對(duì)這樣的改變沒有感知。 為了接受這兩個(gè)事件,我們不需要?jiǎng)?chuàng)建兩個(gè)類,我們只需要像如下這種方式就可以:
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); } }上面我們創(chuàng)建一個(gè)實(shí)例,有兩個(gè)事件處理方法,這樣AllTradeAuditor會(huì)接受所有的Trade事件。 哪個(gè)方法被調(diào)用取決于EventBus發(fā)送什么類型的事件。 為了驗(yàn)證一下,我們可以寫一個(gè)方法接受Object類型的參數(shù),這樣就可以收到所有的事件。
下面我們來考慮一下我們有多個(gè)EventBus實(shí)例。 如果我們把BuySellTradeExecutor類拆分成兩個(gè)類,這樣我們就可以注入兩個(gè)不同的EventBus實(shí)例,但是在訂閱類中就要注意注入的是哪個(gè)類了。 關(guān)于這個(gè)例子我們?cè)谶@里不討論了,代碼可以見
bbejeck.guava.chapter7.config 包。
取消事件訂閱
我們訂閱事件的時(shí)候,肯定也會(huì)想到在某個(gè)時(shí)間點(diǎn)我們需要取消訂閱某個(gè)事件。 取消事件訂閱只需要調(diào)用eventBus.unregister方法。 如果我們知道我們?cè)谀硞€(gè)時(shí)刻想要停止對(duì)某個(gè)事件的處理,我們可以按照如下的方式處理:
public void unregister(){ this.eventBus.unregister(this); }一旦上面的方法被調(diào)用,就不在會(huì)收到任何事件,其他的沒有取消訂閱的會(huì)繼續(xù)收到事件.
異步事件總線
我們之前一直強(qiáng)調(diào)事件處理器的處理邏輯要簡(jiǎn)單。 因?yàn)镋ventBus是順序的處理每一個(gè)事件的。 我們還有另外一種處理方式: AsyncEventBus. AsyncEventBus提供了和EventBus相同的功能。只是在處理事件的時(shí)候采用了Java.util.concurrent.Executor 調(diào)用事件處理器。
創(chuàng)建一個(gè)異步EventBus實(shí)例
創(chuàng)建AsyncEvent和創(chuàng)建一個(gè)EventBus差不多:
AsyncEventBus asyncEventBus = new AsyncEventBus(executorService);我們創(chuàng)建一個(gè)傳入ExecutorService實(shí)例的AsyncEvent實(shí)例。 我們還有一個(gè)接受兩個(gè)參數(shù)的構(gòu)造函數(shù),接受另外一個(gè)string表明ExecutorService的身份。 AysncEventBus在事件處理器需要花費(fèi)時(shí)間比較長(zhǎng)的場(chǎng)景下比較適合。
DeadEvent
當(dāng)一個(gè)事件沒有監(jiān)聽者,我們就會(huì)將這樣的事件包裝成DeadEvent,這樣有一個(gè)方法監(jiān)聽DeadEvent我們就可以知道哪些事件沒有監(jiān)聽者。
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()); } }上面的例子中我們簡(jiǎn)單的注冊(cè)了一個(gè)監(jiān)聽DeadEvent的監(jiān)聽者,簡(jiǎn)單的記錄了沒有被監(jiān)聽的事件。
依賴注入
為了確保我們注冊(cè)的監(jiān)聽者和發(fā)布者是同一個(gè)EventBus實(shí)例,我們使用Spring來實(shí)現(xiàn)EventBus的注入。 在下面的例子中,我們將展示怎樣使用Spring框架去配置SimpleTradeAuditor 和 SimpleTradeExecutor類。首先我們需要對(duì)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); }我們首先在兩個(gè)類上加上了@Component注解。 這樣可以讓Spring把這兩個(gè)類當(dāng)作可以注入的bean。這里我們使用的構(gòu)造方法注入。所以我們加上了@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就把這個(gè)類當(dāng)作Context,在這個(gè)類中我們返回了EventBus實(shí)例。這樣spring就對(duì)上面的兩個(gè)類注入了同一個(gè)EventBus,這正是我們想要的,至于spring是怎么做到的,不在本書考慮的范圍。
總結(jié)
在這一章,我們講解了怎樣使用Guava進(jìn)行事件驅(qū)動(dòng)編程,來降低模塊之間的耦合,我們講解了怎么創(chuàng)建EventBus實(shí)例,并且怎樣注冊(cè)監(jiān)聽者和發(fā)布者。 并且我們也剖析了怎樣更具事件類型去監(jiān)聽事件。 最后我們還學(xué)習(xí)了使用AsyncEventBus類,可以讓我們異步的發(fā)送事件。我們也學(xué)習(xí)了怎樣使用DeadEvent類去確保我們監(jiān)聽了所有的事件。 最后,我們還學(xué)習(xí)了使用依賴注入來使得我們可以更容易創(chuàng)建基于事件的系統(tǒng)。
總結(jié)
以上是生活随笔為你收集整理的Guava翻译系列之EventBus的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大话Linux内核中锁机制之原子操作、自
- 下一篇: 传统存储做到极致也惊人!看宏杉科技发布的