使用Guava EventBus實現Java組件間的通信

在構建企業應用程序時經常出現的一種情況是在一個過程成功後需要實現一系列業務流程,例如,如果您正在構建購物網站,則可能需要在產品付款成功後執行以下操作:

  1. 購買後減少商店中可用的物品數量。
  2. 向商店老闆發送收據。
  3. 向買方發送收據。

在某些情況下,您可能希望這些過程對於產品購買而言是原子性的,也就是說,在發生產品購買之後,必須減少項目數量,如果在減少可用項目數量方面存在問題,則還原產品購買。通常會在事務中執行此操作以使流程原子化。

但在某些情況下,如果第二步失敗了但第二步的重要性可能不足以使其恢復第一步的執行,例如,您可能不希望將收據發送給客戶或賣方的過程影響付款過程。在這種情況下,您可能希望使處理兩個流程的組件儘可能地分離,以便它們可以獨立執行操作,這時事件總線就可以幫助我們來實現。

什麼是事件總線?

事件總線是一種機制,它允許不同的組件彼此通信而不無需相互瞭解。組件可以將事件發送到Eventbus,而無需知道是誰來接聽或有多少人來接聽。組件也可以偵聽Eventbus上的事件,而無需知道誰發送了事件。這樣,組件可以相互通信而無需相互依賴。同樣的也很容易替換其中一個組件。只要新組件瞭解正在發送和接收的事件,而這一過程其他組件永遠不會知道。

在本文中,我們將使用Google Guava EventBus來展示如何在Java應用程序中解耦組件。

Guava EventBus

Guava的EventBus提供了一個發佈-訂閱事件機制,該機制允許對象通過觀察者模式相互通信。EventBus避開了Java中常見的“事件監聽器”模式,在Java中,一個對象實現了特定的接口,然後顯式地向另一個對象訂閱。

EventBus允許組件之間進行發佈-訂閱式通信,而無需組件之間彼此瞭解。它專門設計爲使用顯式註冊代替傳統的Java進程內事件分發。它不是通用的發佈-訂閱系統,也不能用於進程間的通信。

組件解耦

在我們的示例中,我們將通過模擬本文開頭提到的付款過程來演示如何使用事件總線。

我們應用程序涉及的組件:

  • 第一部分應該處理與成功付款直接相關的流程,例如減少商店中剩餘的物品數量。
  • 第二部分處理向客戶發送收據。
  • 第三部分處理向賣方發送收據。

我們可以通過以不同的方法實現這些流程並將它們依次鏈接在一起,我們可以像這樣實現:

但是這種方法的問題在於,這些組件沒有正確地分離,並且在一個過程中發生錯誤時,可能會影響另一個過程。另外,如果我們必須添加更多要在成功付款後執行的流程,則必須手動添加方法調用,這樣做並不好。我們將使用事件總線解決該問題。

這就是我們改進的樣子:

支付組件和收據發送者組件互相完全不知道,事件總線負責將信息從發佈者傳送到所有訂閱者。

Guava EventBus的實現

在展示如何實現前先介紹Guava EventBus中的一些重要概念:

  • 事件總線:這是委派負責將事件數據從一個組件傳送到另一個組件的對象,通常,您需要一個事件總線的實例,以便生產者和訂閱者使用同一事件總線,這是事件總線正常工作所必需的。您可以使用單例模式來確保僅創建此類的一個實例,並在需要事件總線的實例時使用IOC容器提供該實例。

注意:您可能具有Event的多個實例,但是如果您希望不同的事件總線處理不同的組件集,只需要保證發佈者和訂閱者必須使用相同的事件總線來通信。

  • 生產者:生產者負責發出事件,然後將這些事件傳遞到事件總線,併發送給訂閱該事件的所有偵聽器。

  • 偵聽器:偵聽器訂閱事件,並在生產者發佈該事件時觸發該事件,您可以讓偵聽器方法同步或異步運行,具體取決於您使用的事件總線的類型。爲了使同步運行的監聽方法,你可以使用一個同步事件總線是默認EventBus類,要把它異步運行,你將不得不使用AsyncEventBus類,這是一個EventBus子類,它的構造需要一個executor將被用於在單獨的線程上執行該方法。在我們的示例中,我們將使用默認EventBus。

  • 事件:在Guava事件總線中,事件只是使用類名唯一標識的對象。因此,要創建付款成功事件,我們只需創建一個PaymentSuccessfulEvent.java類,並在付款成功後發佈該類的實例。

具體實現

  1. 將Google Guava庫添加到您的項目中
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>28.0-jre</version>
</dependency>
  1. 創建事件總線對象。它可以註冊偵聽器併發布事件。使用它就像實例化類一樣簡單:
EventBus eventBus = new EventBus();
  1. 創建付款成功事件。事件只是一個簡單的類,每個事件都由類的名稱標識。因此,讓我們創建一個PaymentSuccessful事件。
public class PaymentSuccessfulEvent {
    private String action;

    PaymentSuccessfulEvent(String action) {
        this.action = action;
    }

    String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }
}
  1. 爲“付款成功”事件創建偵聽器。在Guava EventBus中,偵聽器是一種用@Subscribe註解修飾的方法,用於接收特定事件,該方法接受與發佈事件相同類型的對象作爲參數。讓我們創建一個付款成功事件監聽器
public class PaymentSuccessfulEventListener {

    private static int eventsHandled;
    private static final Logger LOG = LoggerFactory.getLogger(PaymentSuccessfulEventListener.class);


    @Subscribe
    public void paymentEvent(PaymentSuccessfulEvent event) {
        LOG.info("do event [" + event.getAction() + "]");
        eventsHandled++;
    }


    int getEventsHandled() {
        return eventsHandled;
    }

    void resetEventsHandled() {
        eventsHandled = 0;
    }
}
  1. 註冊事件偵聽者。我們可以通過在EventBus上註冊EventListener類來訂閱事件:
PaymentSuccessfulEventListener listener = new PaymentSuccessfulEventListener();
eventBus.register(listener);
  1. 發佈事件。這將觸發所有將PaymentSuccessfulEvent作爲其參數並且還具有@Subscribe註解的方法。
eventBus.post(paymentEvent);

完整測試類:

public class GuavaEventBusUnitTest {

    private PaymentSuccessfulEventListener listener;
    private EventBus eventBus;

    @Before
    public void setUp() {
        eventBus = new EventBus();
        listener = new PaymentSuccessfulEventListener();

        eventBus.register(listener);
    }

    @After
    public void tearDown() {
        eventBus.unregister(listener);
    }

    @Test
    public void paymentSuccessfulEvent_whenEventHandled_thenSuccess() {
        listener.resetEventsHandled();

        PaymentSuccessfulEvent paymentEvent = new PaymentSuccessfulEvent("PaymentSuccessful Event");
        eventBus.post(paymentEvent);

        assertEquals(1, listener.getEventsHandled());
    }

}

總結

在本文中我們使用了一個簡單的示例講解如何使用Guava EventBus。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章