ABP理論學習之事件總線和領域事件


返回總目錄


本篇目錄

在C#中,我們可以在一個類中定義自己的事件,而其他的類可以注冊該事件,當某些事情發生時,可以通知到該類。這對於桌面應用或者獨立的windows服務來說是非常有用的。但對於一個web應用來說是有點問題的,因為對象都是在web請求中創建的,而且這些對象生命周期都很短,因而注冊某些類的事件是很困難的。此外,注冊其他類的事件會使得類緊耦合。

領域事件用於解耦並重復利用應用中的邏輯。

事件總線###

事件總線是被所有觸發並處理事件的其他類共享的單例對象。要使用事件總線,首先應該獲得它的一個引用。下面有兩種方法來處理:

創建默認實例

你可以直接使用 EventBus.Default。這是全局的事件總線,用法如下所示:

EventBus.Default.Trigger(...); //觸發一個事件

注入IEventBus

不直接使用EventBus.Default,你也可以使用依賴注入來獲得IEventBus的引用。這有利於單元測試。這里我們使用屬性注入模式:

public class TaskAppService : ApplicationService
{
    public IEventBus EventBus { get; set; }
        
    public TaskAppService()
    {
        EventBus = NullEventBus.Instance;
    }
}

對於注入事件總線這件事,屬性注入比構造函數注入更合適。這樣,你的類離開事件總線還能工作。NullEventBus實現了null對象模式。當你調用上面的構造函數時,實際上啥都沒做。

定義事件###

觸發事件之前,應該先要定義該事件。事件是使用派生自EventData的類來表示的。假設我們想當一個任務task完成時觸發一個事件:

public class TaskCompletedEventData : EventData
{
    public int TaskId { get; set; }
}

該類包含了類處理事件需要的屬性。EventData類定義了 EventSource(事件源)和 EventTime(事件觸發時間)屬性。

預定義事件

ABP定義了AbpHandleExceptionData,當自動處理任何異常時都會觸發這個事件。如果你想要獲得更多的關於異常的信息(甚至ABP會自動記錄所有的異常),那么這是特別有用的。注冊這個事件之后,異常發生時就會通知你。

對於實體的更改也有泛型的事件數據類:EntityCreatedEventData ,EntityUpdateEventData EntityDeletedEventData 。它們都定義在 Abp.Event.Bus.Entities命名空間中。當一個實體插入,更新或者刪除時,ABP會自動地觸發這些事件。比如,如果你有一個Person實體,將它注冊到EntityCreatedEventData ,那么當創建的新的Person實體對象插入數據庫時,會收到通知。這些事件也支持繼承。如果Student類派生自Person類,而且你將它注冊到EntityCreatedEventData ,那么當一個Person或者Student插入時,你會收到通知。

觸發事件###

觸發一個事件很簡單,如下所示:

public class TaskAppService : ApplicationService
{
    public IEventBus EventBus { get; set; }
        
    public TaskAppService()
    {
        EventBus = NullEventBus.Instance;
    }

    public void CompleteTask(CompleteTaskInput input)
    {
        //TODO: 完成task的數據庫操作...
        EventBus.Trigger(new TaskCompletedEventData {TaskId = 42});
    }
}


下面是Trigger方法的一些重載:

EventBus.Trigger<TaskCompletedEventData>(new TaskCompletedEventData { TaskId = 42 }); //顯示聲明為泛型參數
EventBus.Trigger(this, new TaskCompletedEventData { TaskId = 42 }); //將 '事件源'設置為'this'
EventBus.Trigger(typeof(TaskCompletedEventData), this, new TaskCompletedEventData { TaskId = 42 });//調用非泛型版本(第一個參數是事件類的類型)

處理事件###

要處理一個事件,應該要實現IEventHandler 接口,如下所示:

public class ActivityWriter : IEventHandler<TaskCompletedEventData>, ITransientDependency
{
    public void HandleEvent(TaskCompletedEventData eventData)
    {
        WriteActivity("A task is completed by id = " + eventData.TaskId);
    }
}

事件總線(EventBus)已經集成到ABP的依賴注入系統中。正如上面實現ITransientDependency一樣,當TaskCompleted事件發生時,它會創建ActivityWriter類的一個新實例,然后調用HandleEvent方法,最后釋放它。更多知識請查看依賴注入

處理基事件

事件總線支持事件的繼承。比如,你創建了一個TaskEventData和它的兩個子類: TaskCompletedEventDataTaskCreatedEventData:

public class TaskEventData : EventData
{
    public Task Task { get; set; }
}

public class TaskCreatedEventData : TaskEventData
{
    public User CreatorUser { get; set; }
}

public class TaskCompletedEventData : TaskEventData
{
    public User CompletorUser { get; set; }
}

然后你可以實現IEventHandler 來處理這兩個事件:

public class ActivityWriter : IEventHandler<TaskEventData>, ITransientDependency
{
    public void HandleEvent(TaskEventData eventData)
    {
        if (eventData is TaskCreatedEventData)
        {
            //...
        }
        else if (eventData is TaskCompletedEventData)
        {
            //...
        }
    }
}


當然了,你可以實現IEventHandler 來處理所有你想要處理的事件。

處理多事件

在一個單一的處理句柄中,可以處理多個事件。這時,你應該為每個事件實現IEventHandler 。比如:

public class ActivityWriter : 
    IEventHandler<TaskCompletedEventData>, 
    IEventHandler<TaskCreatedEventData>, 
    ITransientDependency
{
    public void HandleEvent(TaskCompletedEventData eventData)
    {
        //TODO: 處理事件...
    }

    public void HandleEvent(TaskCreatedEventData eventData)
    {
        //TODO: 處理事件...
    }
}

句柄注冊###

為了處理事件,我們必須將事件句柄注冊給事件總線。

自動

ABP會自動掃描所有的實現了IEventHandler的類,並自動將它們注冊到事件總線上。當一個事件發生時,它會使用依賴注入獲得該句柄的一個引用,而且在處理該事件之后就會釋放該句柄。建議這樣使用ABP中的事件總線。

手動

也可能會手動注冊到事件,但是要小心使用。在一個web應用中,事件注冊應該在應用啟動時完成。在web請求時注冊到一個事件不是一個好的方法,因為請求完成之后注冊的類仍舊是注冊的,而且對於每個請求繼續再次注冊。這可能會對你的應用造成問題,因為注冊的類可能被調用多次。而且要記住手動注冊不會使用依賴注入系統。

這里有一些事件總線的方法的重載。最簡單的一個等待了一個委托(或者一個lambda):

EventBus.Register<TaskCompletedEventData>(eventData =>
    {
        WriteActivity("A task is completed by id = " + eventData.TaskId);
    });

這樣,當“一個task完成”事件發生時,這個lambda方法就會調用。第二個等待一個實現了IEventHandler 的對象:

EventBus.Register<TaskCompletedEventData>(new ActivityWriter());

事件會調用ActivityWriter的相同實例。該方法也有一個非泛型的重載。另一個重載接受兩個泛型的參數:

EventBus.Register<TaskCompletedEventData, ActivityWriter>();

此時,事件總線會為每個事件創建一個新的ActivityWriter。如果它是可釋放的,那么會調用ActivityWriter.Dispose方法。

最后,為了處理句柄的創建,你可以注冊一個事件句柄工廠。句柄工廠有兩個方法:GetHandler和ReleaseHandler。例如:

public class ActivityWriterFactory : IEventHandlerFactory
{
    public IEventHandler GetHandler()
    {
        return new ActivityWriter();
    }

    public void ReleaseHandler(IEventHandler handler)
    {
        //TODO:釋放ActivityWriter實例 (handler)
    }
}

還有一個特殊的工廠類IocHandlerFactory,它可以使用依賴注入系統創建或者釋放句柄。ABP在自動注冊模式中使用了這個類。因此,如果你想使用依賴注入系統,直接使用自動注冊。

取消注冊###

手動注冊到事件總線時,你可能會在以后想要取消注冊該事件。取消注冊一個事件的最簡單方法是釋放該注冊方法的返回值。如下所示:

//注冊到一個事件...
var registration = EventBus.Register<TaskCompletedEventData>(eventData => WriteActivity("A task is completed by id = " + eventData.TaskId) );

//取消注冊事件
registration.Dispose();

當然了,注銷注冊會在某個地方和某個時間。保留注冊對象並在想要取消注冊時釋放它。所有注冊方法的重載都會返回一個可釋放的對象以取消注冊該事件。

事件總線也提供了Unregister方法。樣例用法:

//創建一個句柄
var handler = new ActivityWriter();
            
//注冊到事件
EventBus.Register<TaskCompletedEventData>(handler);

//從事件取消注冊
EventBus.Unregister<TaskCompletedEventData>(handler);

它也提供了重載來注銷委托和工廠,注銷句柄對象必須是之前注冊的相同對象。

最后,事件總線提供了UnregisterAll 方法來注銷一個事件的所有句柄,RegisterAll()方法會注銷所有事件的所有句柄。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM