Guava庫學習:學習Guava EventBus(二)EventBus 事件訂閱
http://my.oschina.net/realfighter/blog/406342
上一篇Guava庫學習:學習Guava EventBus(一)EventBus,我們簡單的對Guava基於事件的程式設計進行了介紹,學習和了解了EventBus類的使用,本篇起,我們通過一系列的示例程式碼深入的學習EventBus類,本篇學習Guava EventBus(二)EventBus 事件訂閱示例。
訂閱Subscribe
首先,我們假定定義瞭如下所示的TradeAccountEvent類,如下:
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 " );
}
//Details left out for clarity
}
每當執行買賣交易時,我們都將建立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);
}
}
這裡簡單的分析一下上面的程式碼。在SimpleTradeAuditor的構造方法中,我們接收了EventBus類的一個例項,並且通過EventBus立即註冊了SimpleTradeAuditor類,來接收TradeAccountEvents類的通知。我們通過在auditTrade方法上新增@Subscribe註解,來指定auditTrade作為事件處理方法。在上面的例子中,我們只是簡單的把TradeAccountEvent物件新增到一個list,並簡單的輸出到控制檯。
釋出Publishing
首先來看下面的示例程式碼,程式碼如下:
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(tradeAccount, amount, executionTime, tradeType);
System.out.println(message);
return tradeAccountEvent;
}
}
與SimpleTradeAuditor類相似,我們也通過EventBus例項,構造了SimpleTradeExecutor。但是與SimpleTradeAuditor類不同的是,我們儲存了一份EventBus的引用以備後用。你可能看到過很多類似的編碼,對於相同例項在兩個類之間的傳遞,這是至關重要的。在以後的示例中,我們將會介紹使用多個EventBus例項,在本篇的例子中, 我們使用單個EventBus例項。
在上面的例子中,SimpleTradeExecutor類,有一個公共的executeTrade方法,它接收了用來處理交易所需要的所有資訊。我們呼叫processTrade方法傳遞所需的資訊,當執行交易的時候列印資訊到控制檯,然後返回一個TradeAccountEvent例項。當processTrade方法執行完成,我們呼叫EventBus.post()方法釋出返回的TradeAccountEvent例項,並通知所有TradeAccountEvent物件的訂閱者。如果我們快速的比較下SimpleTradeAuditor和SimpleTradeExecutor類,我們看到,雖然兩個類都參與共享所需的資訊,但是它們彼此之間沒有任何的耦合。
更細粒度的訂閱
上面我們看到了使用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;
}
}
我們看到,上面的TradeSellAuditor非常類似於SimpleTradeAuditor,不過TradeSellAuditor 只會接收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類的程式碼,使其能夠根據交易的型別來建立正確的TradeAccountEvent例項,程式碼如下:
public class BuySellTradeExecutor {
private EventBus eventBus;
public BuySellTradeExecutor(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;
if (tradeType.equals(TradeType.BUY)) {
tradeAccountEvent = new BuyEvent(tradeAccount, amount,
executionTime);
} else {
tradeAccountEvent = new SellEvent(tradeAccount,
amount, executionTime);
}
System.out.println(message);
return tradeAccountEvent;
}
}
這樣我們就已經建立了一個新的BuySellTradeExecutor類,根據交易的型別,我們將建立相應的BuyEvent或SellEvent例項,它的作用與我們之前的SimpleTradeExecutor類相似。但是,EventBus類是完全沒有意識到這些變化的。我們註冊了不同的訂閱者併發布了不同的事件,這些變化對EventBus類來說是透明的。
注意,我們不需要為這些事件的通知建立單獨的類。我們的SimpleTradeAuditor類會在事件發生時繼續接收這些通知。如果我們想根據事件的型別做單獨的處理,我們可以簡單的新增一個檢查事件的型別。最後,如果需要,我們也可以定義一個類有多個訂閱方法:
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);
}
}
上面我們建立了一個包含兩個事件處理方法的類,AllTradesAuditor方法將接收所有交易事件的通知,它只是一個被EventBus(基於事件型別)呼叫的方法。採取一個極端,我們可以建立一個事件處理方法,該方法接受一個Object型別的物件,Object在java中是所有物件的父類,這樣我們就可以接收任何和所有由EventBus處理的事件的通知了。最後,沒有什麼能夠阻止我們擁有多個EventBus例項。如果我們要重構BuySellTradeExecutor類成兩個獨立的類,我們可以為每個類注入一個單獨的EventBus例項。那麼它將是一個注入正確EventBus例項審計類的方法,我們就有了一套完整獨立的釋出-訂閱事件。
取消訂閱
正如我們想訂閱事件,有些情況下我們可能需要取消事件的訂閱。可以通過訂閱物件的eventbus.unregister方法實現。例如,如果我們需要取消訂閱事件,我們可以將下面的方法新增到我們的訂閱類:
public void unregister(){
this.eventBus.unregister(this);
}
一旦呼叫此方法,該特定例項將停止接收無論多久以前註冊的事件。其他註冊了相同事件的例項則會繼續接收通知。
非同步EventBus
Eventbus處理所有的事件都以序列的方式,這種事件處理方法確保了處理的輕量性。不過,我們仍然有另外的選擇AsyncEventBus,AsyncEventBus類提供了與EcentBus相同的功能,但是使用了java.util.concurrent.executor例項來進行方法的非同步處理。
我們可以通過類似於EventBus例項的方式,建立一個AsyncEventBus例項:
AsyncEventBus asyncEventBus = new AsyncEventBus(executorService);
上面我們通過一個ExecutorService例項建立了AsyncEventBus例項,除了ExecutorService例項,也可以通過提供一個字串識別符號建立AsyncEventBus。當我們的訂閱者在接收事件時需要執行繁重的處理時,使用AsyncEventBus會很有用。
DeadEvents
當EventBus收到事件通過post方法傳送的通知,並且沒有註冊的訂閱者,那麼事件則是被DeadEvent類的一個例項包裹。當試圖確保所有的事件都有註冊的訂閱者時,有一個DeadEvents例項的訂閱類是非常有用的。DeadEvents類提供了一個公共的getEvent方法,可以用來檢查那些未交付的原始事件。例如,我們可以通過下面的方式建立一個非常簡單的例子:
public class DeadEventSubscriber {
public DeadEventSubscriber(EventBus eventBus) {
eventBus.register(this);
}
@Subscribe
public void handleUnsubscribedEvent(DeadEvent deadEvent) {
System.out.println("No subscribers for " + deadEvent.getEvent());
}
}
上面簡單的對任何DeadEvent例項進行了註冊,並記錄了那些沒有訂閱者的事件。
Dependency injection依賴注入
為了確保我們為相同的EventBus例項註冊了訂閱者和釋出者,使用依賴注入框架(Spring或Guice)顯得很有意義。接下來的例子中,我們會介紹怎麼配置Spring框架在SimpleTradeAuditor和SimpleTradeExecutor類。首先,我們對SimpleTradeAuditor和SimpleTradeExecutor類做如下的修改:
@Component
public class SimpleTradeAuditor {
private List<TradeAccountEvent> tradeEvents =
Lists.newArrayList();
@Autowired
public SimpleTradeAuditor(EventBus eventBus) {
eventBus.register(this);
}
@Subscribe
public void auditTrade(TradeAccountEvent tradeAccountEvent) {
tradeEvents.add(tradeAccountEvent);
System.out.println("Received trade " + tradeAccountEvent);
}
}
@Component
public class SimpleTradeExecutor {
private EventBus eventBus;
@Autowired
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(tradeAccount, amount, executionTime, tradeType);
System.out.println(message);
return tradeAccountEvent;
}
}
上面我們簡單的為兩個類添加了類級別的@Component註解,這是為了使Spring將這些我們想注入的類作為bean。這樣,我們就需要使用構造注入,所以在兩個類的構造方法上添加了@Autowired註解,@Autowired告訴Spring給兩個類注入EventBus的一個例項。最後,我們有我們的配置類,來指示Spring框架在哪裡尋找元件,並連線配置類中定義的bean:
@Configuration
@ComponentScan(basePackages = {"guava"})
public class EventBusConfig {
@Bean
public EventBus eventBus() {
return new EventBus();
}
}
上面我們使用了@Configuration註解,它標識了此類作為Spring上下文包含bean的建立和注入。我們定義了eventBus方法構造並且返回了EventBus類的一個例項,它將被注入給其他物件。這種情況下,當我們在SimpleTradeAuditor和SimpleTradeExecutor類的構造方法上使用@Autowire註解,Spring會自動注入相同的EventBus例項,這正是我們所需要的。值得注意的是,Spring預設情況下建立單例類,這也是我們這裡想要的。正如我們所看到的,使用依賴注入框架可以確保我們基於事件的系統配置的合理正確。
Summary
在本篇中,我們已經介紹瞭如何通過Guava EventBus類使用基於事件的程式設計,來減少我們的程式碼耦合。我們介紹瞭如何建立一個EventBus例項並註冊訂閱者和釋出者。我們也探討了強大的使用型別註冊那些我們感興趣的事件。我們瞭解了AsyncEventBus類,它允許我們傳送非同步事件。我們看到了如何使用DeadEvent類,以確保我們的事件都擁有訂閱者。最後,我們看到了如何使用依賴注入框架來解耦我們基於事件的系統配置。
下一個系列中, 我們將會學習如何通過Guava對檔案進行操作。敬請關注。