Abp 业务异常源码解读


Abp 业务异常源码解读

最近一直在读代码整洁之道,我在读到第三章函数的3.9 使用异常替代返回错误码,其实在我的开发经历中都是使用返回错误码给到前端,之前在阅读ABP官网文档中就有看到过使用异常替代异常的做法,当时自己还是比较抵触,在读完本章之后我们就马上阅读了Abp的异常处理源码。


ABP 提供了一个内置的基础设施,并提供了一个标准模型来处理异常。

  • 自动处理所有异常并向客户端发送标准格式的错误消息以获取 API/AJAX 请求。
  • 自动隐藏内部基础架构错误并返回标准错误消息。
  • 提供一种简单且可配置的方式来本地化异常消息,可以实现多语言返回。
  • 自动将标准异常映射到HTTP 状态代码,并提供一个可配置的选项来映射自定义异常。

源码
源码

我们首先可以看到AbpExceptionFilter继承我们的异常拦截器,依赖注入的生命周期是瞬态的

// 我们首先可以看到AbpExceptionFilter继承我们的异常拦截器,依赖注入的生命周期是瞬态的
public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency
{
   ·····省略代码
}

AbpExceptionFilter如果满足以下任何条件,则处理异常:

  • 异常由返回对象结果(不是视图结果)的控制器操作引发。
  • 该请求是一个 AJAX 请求(X-Requested-WithHTTP 标头值为XMLHttpRequest)。
  • 客户端明确接受application/json内容类型(通过acceptHTTP 标头)。

如果异常得到处理,它会自动记录下来,并将格式化的JSON 消息返回给客户端。

   // 判断当前请求的异常是否需要自动处理
    protected virtual bool ShouldHandleException(ExceptionContext context)
    {
        // 1.判断当前请求是否是控制器方法
        // 2.并且有返回结果
        if (context.ActionDescriptor.IsControllerAction() &&
            context.ActionDescriptor.HasObjectResult())
        {
            return true;
        }
        // 1.当前请求中头accept是否是application/json内容类型
        if (context.HttpContext.Request.CanAccept(MimeTypes.Application.Json))
        {
            return true;
        }
        // 1.当前请求是否是AJAX 请求
        if (context.HttpContext.Request.IsAjax())
        {
            return true;
        }

        return false;
    }

如果ShouldHandleException()方法返回 true就会进入HandleAndWrapException() 自动格式化处理异常方法

    // 自动格式化处理异常
    protected virtual async Task HandleAndWrapException(ExceptionContext context)
    {
        //TODO: Trigger an AbpExceptionHandled event or something like that.
        // 1.首先还是老样子读取当前模块的配置信息
        var exceptionHandlingOptions = context.GetRequiredService>().Value;
        // 2.获取异常格式转换器,因为需要将我们的异常格式化,多语言实现也是在这个格式化转换器中实现的
        var exceptionToErrorInfoConverter = context.GetRequiredService();
        // 3.通过格式化转换器,将异常信息转换成为前端展示数据(这里就会使用到我们的配置信息)
        var remoteServiceErrorInfo = exceptionToErrorInfoConverter.Convert(context.Exception, options =>
       {
           // 是否向客户端发送异常详细信息(默认是false)
           options.SendExceptionsDetailsToClients = exceptionHandlingOptions.SendExceptionsDetailsToClients;
           // 发送堆栈跟踪到客户端(默认是true)
           options.SendStackTraceToClients = exceptionHandlingOptions.SendStackTraceToClients;
       });
        // 4.获取我们业务异常日志等级
        var logLevel = context.Exception.GetLogLevel();
        // 5.创建一个StringBuilder对象拼接异常信息
        var remoteServiceErrorInfoBuilder = new StringBuilder();
        remoteServiceErrorInfoBuilder.AppendLine($"---------- {nameof(RemoteServiceErrorInfo)} ----------");
        remoteServiceErrorInfoBuilder.AppendLine(context.GetRequiredService().Serialize(remoteServiceErrorInfo, indented: true));
        
        // 6.获取日志信息
        var logger = context.GetService>(NullLogger.Instance);
        
        logger.LogWithLevel(logLevel, remoteServiceErrorInfoBuilder.ToString());

        logger.LogException(context.Exception, logLevel);

        // 7.获取注入IExceptionNotifier接口的实现类,给IExceptionSubscriber实现类接口批量发送事件
        await context.GetRequiredService().NotifyAsync(new ExceptionNotificationContext(context.Exception));
        
        // 8.判断当前异常是不是身份认证异常
        if (context.Exception is AbpAuthorizationException)
        {
            await context.HttpContext.RequestServices.GetRequiredService()
                .HandleAsync(context.Exception.As(), context.HttpContext);
        }
        else
        {
            // 9.添加请求头标识_AbpErrorFormat(给告诉调用者,这次的异常已经是被我们格式化的)
            context.HttpContext.Response.Headers.Add(AbpHttpConsts.AbpErrorFormat, "true");
            // 10.设置返回状态码
            context.HttpContext.Response.StatusCode = (int)context
                .GetRequiredService()
                .GetStatusCode(context.HttpContext, context.Exception);
            // 11.将我们序列化好的错误信息放入请求返回结果中
            context.Result = new ObjectResult(new RemoteServiceErrorResponse(remoteServiceErrorInfo));
        }
        // 12.清空当前请求的异常
        context.Exception = null; //Handled!
    }

Abp 官方文档

  • Abp 源码