好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

ABP框架中的事件总线功能介绍

事件总线

关于事件总线

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://github.com/whuanle/AbpBaseStruct

到此这篇关于ABP框架中的事件总线功能的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。

查看更多关于ABP框架中的事件总线功能介绍的详细内容...

  阅读:50次