1. 程式人生 > >Guava庫學習:學習Guava EventBus(二)EventBus 事件訂閱

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對檔案進行操作。敬請關注。