浅入 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.Web
的 Logs
目录中,打开 -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):事件总线的更多相关文章
-
浅入ABP(1):搭建基础结构的 ABP 解决方案
浅入ABP(1):搭建基础结构的 ABP 解决方案 目录 浅入ABP(1):搭建基础结构的 ABP 解决方案 搭建项目基础结构 ApbBase.Domain.Shared 创建过程 ApbBase.D ...
-
ABP理论学习之事件总线和领域事件
返回总目录 本篇目录 事件总线 定义事件 触发事件 处理事件 句柄注册 取消注册 在C#中,我们可以在一个类中定义自己的事件,而其他的类可以注册该事件,当某些事情发生时,可以通知到该类.这对于桌面应用 ...
-
ABP EventBus(事件总线)
事件总线就是订阅/发布模式的一种实现 事件总线就是为了降低耦合 1.比如在winform中 到处都是事件 触发事件的对象 sender 事件的数据 e 事件的处理逻辑 方法体 通过E ...
-
Android 开发 框架系列 EventBus 事件总线
介绍 GitHub:https://github.com/greenrobot/EventBus 先聊聊EventBus 线程总线是干什么的,使用环境,优点.缺点. 干什么的? 一句话,简单统一数据传 ...
-
VUE 入坑系列 一 事件
html代码 <div id="app"> <button v-on:click="counter += 1">加1</butto ...
-
ABP之事件总线(5)
前面已经对Castle Windsor的基本使用进行了学习,有了这个基础,接下来我们将把我们的事件总线再次向ABP中定义的事件总线靠近.从源码中可以知道在ABP中定义了Dictionary,存放三种类 ...
-
ABP官方文档翻译 3.7 领域事件(事件总线)
领域事件(事件总线) 事件总线 注入IEventBus 获取默认实例 定义事件 预定义事件 处理异常 实体更改 触发事件 处理事件 处理基础事件 处理者异常 处理多个事件 注册处理者 自动 手动 取消 ...
-
[Abp 源码分析]九、事件总线
0.简介 事件总线就是订阅/发布模式的一种实现,本质上事件总线的存在是为了降低耦合而存在的. 从上图可以看到事件由发布者发布到事件总线处理器当中,然后经由事件总线处理器调用订阅者的处理方法,而发布者和 ...
-
ABP之事件总线(4)
在上一篇的随笔中,我们已经初步完成了EventBus,但是EventBus中还有诸多的问题存在,那么到底有什么问题呢,接下来我们需要看一看ABP中的源码是如何定义EventBus的. 1.第一个点 在 ...
随机推荐
-
利用Python进行数据分析(5) NumPy基础: ndarray索引和切片
概念理解 索引即通过一个无符号整数值获取数组里的值. 切片即对数组里某个片段的描述. 一维数组 一维数组的索引 一维数组的索引和Python列表的功能类似: 一维数组的切片 一维数组的切片语法格式为a ...
-
用JAVA写查询一个字符串中是否包含另外一个字符串以及出现的次数
package JAVA; import java.awt.List;import java.util.ArrayList;/** * * @author 梁小鱼 * */public class ...
-
Selenium Webdriver元素定位的常用方式
单选框.复选框.文本框和密码框的元素标签都是input,此时单靠tagName无法准确地得到我们想要的元素,需要结合type属性才能过滤出我们要的元素.示例代码如下: public class Sea ...
-
【BZOJ】1082: [SCOI2005]栅栏(二分+dfs)
http://www.lydsy.com/JudgeOnline/problem.php?id=1082 题意:n个给出木板,m个给出木板.可以将那m个木板锯成泥想要的长度.问最大能锯成多少个给出的n ...
-
使用AnkhSvn-2.5.12478.msi管理vs2013代码的工具安装步骤使用
安装好AnkhSvn后,按照上面红色画出来的图,进行操作: 需要安装的文件有: AnkhSvn-2.5.12478.msi LanguagePack_1.8.5.25224-x64-zh_CN.msi ...
-
玩具装箱 bzoj1010 斜率优化
斜率优化的题好像都是这样的方程:左边关于j,k的一个(...)/(...)的式子,右边是个只与i有关的可算的数字: 然后把它放到二维坐标轴上,用单调队列维护一个凸壳,O(n)的复杂度: 这道题但是我发 ...
-
Spring基础知识之依赖注入
Spring框架的四大原则: 1)使用POJO进行轻量级和最小侵入式的开发. 2)通过依赖注入和基于接口编程实现松耦合. 3)通过AOP和默认习惯进行声明式编程. 4)使用AOP和模板(templat ...
-
mybatis的Mapper文件配置
一.resultMap resultMap 元素是 MyBatis 中最重要最强大的元素. 该配置节点下如下子节点配置 id – 一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能 const ...
-
Android ColorMatrix类图像颜色处理-黑白老照片、泛黄旧照片、高对比度等效果
在Android中,对图像进行颜色方面的处理,如黑白老照片.泛黄旧照片.高对比度.低饱和度等效果,都可以通过使用颜色矩阵(ColorMatrix)来实现. 1.颜色矩阵(ColorMatrix)介绍 ...
-
shell 查找与替换
grep sed 如果想把一个字符串中的一些字符删除可以如此:#Echo “2006-11-21 22:16:30” | sed ‘s/-//g’ | sed ‘s/ //g’ | sed ‘s/:/ ...