Guava翻譯系列之EventBus

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類去確保我們監聽了所有的事件。 最后,我們還學習了使用依賴注入來使得我們可以更容易創建基于事件的系統。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/541807.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/541807.shtml
英文地址,請注明出處:http://en.pswp.cn/news/541807.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java PipedOutputStream close()方法與示例

PipedOutputStream類close()方法 (PipedOutputStream Class close() method) close() method is available in java.io package. close()方法在java.io包中可用。 close() method is used to close this PipedOutputStream and free all system resources linked with this str…

Java二維數組谷電,java二維數組遍歷的2種代碼

二維數組遍歷&#xff1a;思想&#xff1a;1.先將二維數組中所有的元素拿到2.再將二維數組中每個元素進行遍歷&#xff0c;相當于就是在遍歷一個一維數組第一種方法&#xff1a;雙重for循環//遍歷二維數組public class Traverse_a_two_dimensional_array {public static void m…

【轉】MyEclipse快捷鍵大全

常用快捷鍵 -------------------------------------MyEclipse 快捷鍵1(CTRL)-------------------------------------Ctrl1 快速修復CtrlD: 刪除當前行 CtrlQ 定位到最后編輯的地方 CtrlL 定位在某行 CtrlO 快速顯示 OutLine CtrlT 快速顯示當前類的繼承結構 CtrlW 關閉當…

Java整數類的compareTo()方法和示例

整數類compareTo()方法 (Integer class compareTo() method) compareTo() method is available in java.lang package. compareTo()方法在java.lang包中可用。 compareTo() method is used to check equality or inequality for this Integer object against the given Integer…

MATLAB元胞自動機報告,元胞自動機概述與MATLAB實現

什么是元胞自動機&#xff1f;元胞自動機(cellular automata&#xff0c;CA) 是一種時間、空間、狀態都離散&#xff0c;空間相互作用和時間因果關系為局部的網格動力學模型&#xff0c;具有模擬復雜系統時空演化過程的能力。它能構建隨時間推移發生狀態轉移的系統&#xff0c;…

python(33)多進程和多線程的區別

多線程可以共享全局變量&#xff0c;多進程不能。多線程中&#xff0c;所有子線程的進程號相同&#xff1b;多進程中&#xff0c;不同的子進程進程號不同。 #!/usr/bin/python # -*- coding:utf-8 -*- import os import threading import multiprocessing count_thread 0 coun…

Java FilterInputStream reset()方法與示例

FilterInputStream類的reset()方法 (FilterInputStream Class reset() method) reset() method is available in java.io package. reset()方法在java.io包中可用。 reset() method is used to reset this FilterInputStream to the position set by the most recent call of m…

不同php文件,php-不同文件夾的不同登錄(會話)

我有一個Web服務,需要用戶登錄并創建標準$_SESSION [‘XXX’]個用戶變量.我想為應用程序創建一個“演示”,因此為它創建了另一個文件夾.相同的代碼在那里,除了數據庫以外的所有東西.問題是,當用戶登錄這兩個帳戶之一時,它可以訪問兩個帳戶.因此,如果他登錄了演示應用程序,它將使…

Java Hashtable containsValue()方法與示例

哈希表類containsValue()方法 (Hashtable Class containsValue() method) containsValue() method is available in java.util package. containsValue()方法在java.util包中可用。 containsValue() method is used to check whether this table Hashtable associated one or m…

php session redis db,php session redis 配置

具體環境&#xff1a;一臺apachephp的服務器(yum安裝remi源及配置 httpd-2.2.15 php-5.4.45)一臺redis服務器(yum安裝remi源及配置 redis-3.2.6)保證apache服務器可以訪問redis服務器的6379端口具體步驟&#xff1a;1、在apachephp服務器上安裝redis擴展點擊(此處)折疊或打開yu…

sigprocmask, sigpending, sigsuspend的用法

sigset_t set sigemptyset(&set) :清空阻塞信號集合變量 sigfillset(&set) &#xff1a;添加所有的信號到阻塞集合變量里 sigaddset(&set,SIGINT):添加單一信號到阻塞信號集合變量 sigdelset(&set,SIGINT):從阻塞信號集合變量中刪除單一信號 void handler(int …

Java Calendar getDisplayName()方法與示例

日歷類的getDisplayName()方法 (Calendar Class getDisplayName() method) getDisplayName() method is available in java.util package. getDisplayName()方法在java.util包中可用。 getDisplayName() method is used to return string denotation of the given calendar fie…

matlab dir數,DIR - matlab函數

DIR List directory.DIR directory_name lists the files in a directory. Pathnames andwildcards may be used. For example, DIR *.m lists all the M-filesin the current directory.D DIR(‘directory_name‘) returns the results in an M-by-1structure with the field…

(四)其他的說明

2019獨角獸企業重金招聘Python工程師標準>>> 關于日志&#xff0c;主要是利用aop來實現的。cn.demoframe.test.frame.service.LogAspect&#xff0c;這里在方法前做了個切面setReqReachTime&#xff0c;設置了一個請求達到時間。接下來還有個切面&#xff0c;是在co…

Java LocalDate類| 帶示例的compareTo()方法

LocalDate類compareTo()方法 (LocalDate Class compareTo() method) compareTo() method is available in java.time package. compareTo()方法在java.time包中可用。 compareTo() method is used to compare this LocalDate object to the given object. compareTo()方法用于將…

vm中linux物理內存不足解決方案

為什么80%的碼農都做不了架構師&#xff1f;>>> 之前創建的一個center os,默認是8GB&#xff0c;經過一頓折磨&#xff0c;裝jdk,tomcat,redis,mycat,nginx,mysql,hadoop...終于&#xff0c;內存不足了&#xff0c;在使用docker build某鏡像的時候。迭代懵逼了&am…

matlab7.0 6.5,任何處理matlab6.5與7.0.1的兼容問題

mdl文件在6.5里面做的&#xff0c;但是到了7.0里面卻打不開&#xff0c;下面就是相關信息&#xff1a;Warning: Unable to load model file d:\MATLAB7\work\*.mdl. Run "bdclose all; set_param(0, CharacterEncoding, Enc)" where Enc is one of windows-1252, I…

Java BigInteger類| 帶有示例的減去()方法

BigInteger類減去()方法 (BigInteger Class subtract() method) subtract() method is available in java.math package. exclude()方法在java.math包中可用。 subtract() method is used to subtract the given value from the value of this BigInteger. exclude()方法用于從…

php刪除第一個字母,php – 正在上傳的文件將第一個字母切斷

我正在將網站從具有WS2003,IIS6,PHP 5.2的服務器遷移到具有WS2008,IIS7和PHP 5.3的服務器我有一個html表單,上傳文件到網站.if(isset($_POST["Upload"])){echo "";print_r($_POST);print_r($_FILES);echo "";}?>在舊服務器上工作得很好,但在…

.7z.001,.7z.002這樣的文件如何解壓

1 如圖所示&#xff0c;壓縮分卷沒有顯示關聯的軟件來打開&#xff0c;Winrar右擊也無法解壓 2 可以使用7-ZIP軟件打開該文件&#xff0c;然后選擇提取&#xff08;相當于Winrar的解壓&#xff09;&#xff0c;然后選擇提取路徑&#xff0c;默認是同一個文件夾&#xff0c;點擊…