Guava庫學習:學習Guava EventBus(二)EventBus 事件訂閱示例

    原文地址:Guava庫學習:學習Guava EventBus(二)EventBus 事件訂閱示例html

    上一篇Guava庫學習:學習Guava EventBus(一)EventBus,咱們簡單的對Guava基於事件的編程進行了介紹,學習和了解了EventBus類的使用,本篇起,咱們經過一系列的示例代碼深刻的學習EventBus類,本篇學習Guava EventBus(二)EventBus 事件訂閱示例。java

    訂閱Subscribeweb

    首先,咱們假定定義了以下所示的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,並簡單的輸出到控制檯。框架

    發佈Publishingless

    首先來看下面的示例代碼,代碼以下:異步

    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實例。async

    在上面的例子中,SimpleTradeExecutor類,有一個公共的executeTrade方法,它接收了用來處理交易所須要的全部信息。咱們調用processTrade方法傳遞所需的信息,當執行交易的時候打印信息到控制檯,而後返回一個TradeAccountEvent實例。當processTrade方法執行完成,咱們調用EventBus.post()方法發佈返回的TradeAccountEvent實例,並通知全部TradeAccountEvent對象的訂閱者。若是咱們快速的比較下SimpleTradeAuditor和SimpleTradeExecutor類,咱們看到,雖然兩個類都參與共享所需的信息,可是它們彼此之間沒有任何的耦合。post

    更細粒度的訂閱

    上面咱們看到了使用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對文件進行操做。敬請關注。

相關文章
相關標籤/搜索