浅入 ABP 系列(4):事件总线

版权护体作者:痴者工良,微信公众号转载文章需要 《NCC开源社区》同意。

这一篇将来学习 ABP 中的事件总线,然后结合在我们的基架项目中,逐渐构建一个完整的系统。

源码地址:https://github.com/whuanle/AbpBaseStruct

事件总线

关于事件总线

ABP 中,为了方便进程间通讯,给开发者提供了一个叫 事件总线 的功能,事件总线分为 本地事件总线分布式事件总线,本篇文章讲的是 本地事件总线,系列教程中暂时不考虑讲解 分布式事件总线

事件总线 需要使用 Volo.Abp.EventBus 库,ABP 包中自带,不需要额外引入。

事件总线是通过 订阅-发布 形式使用的,某一方只需要按照格式推送事件,而不需要关注是谁接收了事件和如何处理事件。

你可以参考官方文档:https://docs.abp.io/zh-Hans/abp/latest/Local-Event-Bus

为什么需要这个东西

首先列举一下,你工作开发的项目中,编写 控制器时,是不是有这几种代码。

// 记录日志 1
Task.Run(()=>
{
_apiLog.Info($"xxxxxxxx");
});
// 记录日志 2
catch(Exception ex)
{
_apiLog.Error(ex);
}
// 记录日志 3
_apiLog.Info($"登陆信息:用户 [{userName}({clientAdrress})]\);

笔者认为,改善的上述问的方法之一是将函数的功能跟记录日志分开,函数执行任务时,只需要把状态和信息通过事件总线推送,而不需要了关注应该如何处理这些内容。

另外,还有当函数执行某些步骤时,产生了事件,开发者喜欢 new Thread 一个新的线程去执行别的任务,或者 Task.Run

其实,通过事件总线,我们更加好地隔离代码,遵从 单一职责原则 。当然还有很多方面值得使用事件总线,这里我们就不再扯淡了。

前面,我们编写了全局异常拦截器,还有日志组件,这一篇我们将通过事件总线,将 Web 程序的一些部件组合起来。

事件总线创建过程

订阅事件

创建一个服务来订阅事件,当程序中发生某种事件时,此服务将被调用。

事件服务必须继承 ILocalEventHandler<in TEvent> 接口,并实现以下函数:

Task HandleEventAsync(TEvent eventData);

一个系统中,事件服务可以有多个,每个服务的 TEvent 类型不能相同,因为 TEvent 的类型是调用服务的标识。当发生 TEvent 事件后,系统通过 TEvent 去找到这个服务。

事件服务创建完毕后,需要加入到依赖注入中,你可以多继承一个 ITransientDependency 接口,然后统一扫描程序集加入到 依赖注入容器中(第三篇提到过)。

事件

即上面提到的 TEvent

假设有一个系统中所有的事件服务都放到一个容器中,发布者只能传递一个事件,而不能指定谁来提供响应服务。

容器是通过 TEvent 来查找服务的。

事件就是一个模型类,也可以使用 int或者 string 等简单类型(请不要用简单类型做事件),用于传递信息。

一般使用 Event 做后缀。

发布事件

如果需要发布一个事件,只需要注入 ILocalEventBus 即可。

        private readonly ILocalEventBus _localEventBus;

        public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}

然后发布事件:

            await _localEventBus.PublishAsync(
new TEvent
{
... ...
}
);

全局异常加入事件总线功能

创建事件

AbpBase.Web 中,创建一个 Handlers 目录,再在 Handlers 目录下,创建 HandlerEvents 目录。

然后在 HandlerEvents 目录,创建一个 CustomerExceptionEvent.cs 文件。

CustomerExceptionEvent 作为一个异常事件,用于传递异常的信息,而不仅仅是将 Exception ex 记录就了事。

其文件内容如下:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text; namespace AbpBase.Application.Handlers.HandlerEvents
{
/// <summary>
/// 全局异常推送事件
/// </summary>
public class CustomerExceptionEvent
{
/// <summary>
/// 只记录异常
/// </summary>
/// <param name="ex"></param>
public CustomerExceptionEvent(Exception ex)
{
Exception = ex;
} /// <summary>
/// 此异常发生时,用户请求的路由地址
/// </summary>
/// <param name="ex"></param>
/// <param name="actionRoute"></param>
public CustomerExceptionEvent(Exception ex, string actionRoute)
{
Exception = ex;
Action = actionRoute;
} /// <summary>
/// 此异常发生在哪个类型的方法中
/// </summary>
/// <param name="ex"></param>
/// <param name="method"></param>
public CustomerExceptionEvent(Exception ex, MethodBase method)
{
Exception = ex;
MethodInfo = (MethodInfo)method;
} /// <summary>
/// 记录异常信息
/// </summary>
/// <param name="ex"></param>
/// <param name="actionRoute"></param>
/// <param name="method"></param>
public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method)
{
Exception = ex;
Action = actionRoute;
MethodInfo = (MethodInfo)method;
} /// <summary>
/// 当前出现位置
/// <example>
/// <code>
/// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod();
/// </code>
/// </example>
/// </summary>
public MethodInfo MethodInfo { get; private set; } /// <summary>
/// 发生异常的 Action
/// </summary>
public string Action { get; private set; } /// <summary>
/// 具体异常
/// </summary>
public Exception Exception { get; private set; }
}
}

订阅事件

订阅事件,即将其定义为事件的响应者、服务提供者。

当异常发生后,异常的位置,推送异常信息,那么谁来处理这些信息呢?是订阅者。

这里我们定义一个异常日志处理类,来处理程序推送的异常信息。

AbpBase.Web 项目的 Handlers 目录中,添加一个 CustomerExceptionHandler 类,继承:

public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency

服务要处理事件,必须继承 ILocalEventHandler<T>,而 ITransientDependency 是为了此服务可以可以自动注入到容器中。

其文件内容如下:

using AbpBase.Application.Handlers.HandlerEvents;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus; namespace AbpBase.Application.Handlers
{
/// <summary>
/// 全局异常记录日志
/// </summary>
public class CustomerExceptionHandler : ILocalEventHandler<CustomerExceptionEvent>, ITransientDependency
{
private readonly ILogger _ILogger; public CustomerExceptionHandler(ILogger logger)
{
_ILogger = logger;
} public async Task HandleEventAsync(CustomerExceptionEvent eventData)
{
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.AppendLine();
stringBuilder.Append("Action: ");
stringBuilder.AppendLine(eventData.Action);
if (eventData.MethodInfo != null)
{
stringBuilder.Append("Class-Method: ");
stringBuilder.Append(eventData.MethodInfo?.DeclaringType.FullName);
stringBuilder.AppendLine(eventData.MethodInfo?.Name);
} stringBuilder.Append("Source: ");
stringBuilder.AppendLine(eventData.Exception.Source);
stringBuilder.Append("TargetSite: ");
stringBuilder.AppendLine(eventData.Exception.TargetSite?.ToString());
stringBuilder.Append("InnerException: ");
stringBuilder.AppendLine(eventData.Exception.InnerException?.ToString());
stringBuilder.Append("Message: ");
stringBuilder.AppendLine(eventData.Exception.Message);
stringBuilder.Append("HelpLink: ");
stringBuilder.AppendLine(eventData.Exception.HelpLink);
_ILogger.Fatal(stringBuilder.ToString());
await Task.CompletedTask;
}
}
}

这样写,记录的日志可以有很好的层次结构。

发布事件

定义了事件的格式和定义服务来订阅事件后,我们来创建一个发布者。

我们修改一下 WebGlobalExceptionFilter

增加依赖注入:

        private readonly ILocalEventBus _localEventBus;

        public WebGlobalExceptionFilter(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}

发布事件:

        public async Task OnExceptionAsync(ExceptionContext context)
{ if (!context.ExceptionHandled)
{
await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception,
context.ActionDescriptor?.DisplayName));
...
...

测试

创建一个 Action :

        [HttpGet("/T4")]
public string MyWebApi4()
{
int a = 1;
int b = 0;
int c = a / b;
return c.ToString();
}

然后访问 https://localhost:5001/T4 ,会发现请求后报错

AbpBase.WebLogs 目录中,打开 -Fatal.txt 文件。

可以看到:

2020-09-16 18:49:27.750 +08:00 [FTL]
Action: ApbBase.HttpApi.Controllers.TestController.MyWebApi4 (ApbBase.HttpApi)
Source: ApbBase.HttpApi
TargetSite: System.String MyWebApi4()
InnerException:
Message: Attempted to divide by zero.
HelpLink:

除了异常信息外,我们还可以很方便的知道异常发生在 TestController.MyWebApi4 这个位置。

记录事件

如果在普通方法里面出现异常,我们这样这样记录:

            catch (Exception ex)
{
...
new CustomerExceptionEvent(ex, MethodBase.GetCurrentMethod());
...
}

MethodBase.GetCurrentMethod() 可以获取当前正在运行的方法,获得信息后将此参数传递给异常记录服务,会自动解析出具体是哪个地方发生异常。

由于目前 Web 程序中还没有编写什么服务,因此我们先结合到异常日志功能中,后面编写服务时,会再次用到事件总线。

完整代码参考:https://github.com/whuanle/AbpBaseStruct/tree/master/src/4/AbpBase

下一篇文章地址是 https://www.cnblogs.com/whuanle/p/13061059.html

浅入 ABP 系列(4):事件总线的更多相关文章

  1. 浅入ABP(1):搭建基础结构的 ABP 解决方案

    浅入ABP(1):搭建基础结构的 ABP 解决方案 目录 浅入ABP(1):搭建基础结构的 ABP 解决方案 搭建项目基础结构 ApbBase.Domain.Shared 创建过程 ApbBase.D ...

  2. ABP理论学习之事件总线和领域事件

    返回总目录 本篇目录 事件总线 定义事件 触发事件 处理事件 句柄注册 取消注册 在C#中,我们可以在一个类中定义自己的事件,而其他的类可以注册该事件,当某些事情发生时,可以通知到该类.这对于桌面应用 ...

  3. ABP EventBus(事件总线)

    事件总线就是订阅/发布模式的一种实现    事件总线就是为了降低耦合 1.比如在winform中  到处都是事件 触发事件的对象  sender 事件的数据    e 事件的处理逻辑  方法体 通过E ...

  4. Android 开发 框架系列 EventBus 事件总线

    介绍 GitHub:https://github.com/greenrobot/EventBus 先聊聊EventBus 线程总线是干什么的,使用环境,优点.缺点. 干什么的? 一句话,简单统一数据传 ...

  5. VUE 入坑系列 一 事件

    html代码 <div id="app"> <button v-on:click="counter += 1">加1</butto ...

  6. ABP之事件总线(5)

    前面已经对Castle Windsor的基本使用进行了学习,有了这个基础,接下来我们将把我们的事件总线再次向ABP中定义的事件总线靠近.从源码中可以知道在ABP中定义了Dictionary,存放三种类 ...

  7. ABP官方文档翻译 3.7 领域事件(事件总线)

    领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...

  8. [Abp 源码分析]九、事件总线

    0.简介 事件总线就是订阅/发布模式的一种实现,本质上事件总线的存在是为了降低耦合而存在的. 从上图可以看到事件由发布者发布到事件总线处理器当中,然后经由事件总线处理器调用订阅者的处理方法,而发布者和 ...

  9. ABP之事件总线(4)

    在上一篇的随笔中,我们已经初步完成了EventBus,但是EventBus中还有诸多的问题存在,那么到底有什么问题呢,接下来我们需要看一看ABP中的源码是如何定义EventBus的. 1.第一个点 在 ...

随机推荐

  1. Spring Boot系列(一):Spring Boot快速开始

    一.Spring Boot介绍 Spring Boot可以很容易的创建可直接运行的独立的基于Spring的应用程序. 功能特点: 创建独立的Spring应用程序: 直接嵌入Tomcat.Jetty等W ...

  2. ARM伪指令与伪操作

    一.伪指令 ARM伪指令有四个,分别是LDR.ADR.ADRL和NOP,下边对其分别介绍. 1.1 LDR LDR 伪指令用于加载 32 位的立即数或一个地址值到指定寄存器 .形式如  LDR{con ...

  3. 走正确的路 - IT业没有护城河 - 机器翻译新锐Deepl

    最近发生了一件很令我震惊的事情:新的一个机器翻译网站出现了 - www.deepl.com (DeepL 或许会成为你今年首选的翻译工具) 机器翻译早就是红海市场了.我就不从1954年IBM发布俄翻英 ...

  4. Docker 的前世今生

    虚拟化 「要解释清楚 Docker,首先要解释清楚容器(Container)的概念」.要解释容器的话,就需要从操作系统说起.操作系统太底层,细说的话一两本书都说不清楚.这里就一句话来总结一下:操作系统 ...

  5. Java不可重入锁和可重入锁的简单理解

    基础知识 Java多线程的wait()方法和notify()方法 这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出Ille ...

  6. Kubernetes 多集群在开源项目 KubeSphere 的应用

    Kubernetes 多集群使用场景 随着容器的普及和 Kubernetes 的日渐成熟,企业内部运行多个 Kubernetes 集群已变得颇为常见.概括起来,多个集群的使用场景主要有以下几种. 多集 ...

  7. 常见面试题之操作系统中的LRU缓存机制实现

    LRU缓存机制,全称Least Recently Used,字面意思就是最近最少使用,是一种缓存淘汰策略.换句话说,LRU机制就是认为最近使用的数据是有用的,很久没用过的数据是无用的,当内存满了就优先 ...

  8. Http请求-okhttp3基本用法

    简介 HTTP是现代应用常用的一种交换数据和媒体的网络方式,高效地使用HTTP能让资源加载更快,节省带宽.OkHttp是一个高效的HTTP客户端,它有以下默认特性: 支持HTTP/2,允许所有同一个主 ...

  9. 使用Apache的反向代理会影响搜索引擎的收录和排名吗

    http://www.wocaoseo.com/thread-292-1-1.html 百度官方观点:Baiduspider对站点的抓取方式和普通用户访问一样,只要普通用户能访问到的内容,我们就能抓取 ...

  10. Tesseract OCR 安装尝试

    1.简介 Tesseract是一个图像识别项目,将图中的文字识别出来.将一个.jpg .png 等等 的图片作为输入,.txt作为识别内容输出 Tesseract项目GitHub地址 2.安装 你可以 ...