Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 743|回复: 0

基于AOP的MVC拦截异常让代码更优美

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-4-28 14:52:12 | 显示全部楼层 |阅读模式

    与asp.net 打交道很多年,如今天微软的优秀框架越来越多,其中微软在基于mvc的思想架构,也推出了自己的一套asp.net mvc 框架,如果你亲身体验过它,会情不自禁的说‘漂亮’。回过头来,‘漂亮’终归有个好的思想,其中类似于AOP的思想,就在其中体现的淋漓尽致,今天本文主要讨论的是基于AOP思想构成的‘异常过滤器’。我们的目的只有一个,让try...catch...无处盾形,让代码更健壮优美。

     

    一、理解mvc里filter是怎么运行的

     

    老外的一篇文章是这样的草图

    通过翻译中文是这样的

    其中有一个异常过滤器

    通过上述的表格可以清楚的看出,当Controller或Action执行时,IExceptionFiter的实现基类都将有‘能力’处理的,其中微软在mvc中默认实现了一个实现类HandleErrorAttribute

    看看这个的源码是怎么能出的

    public virtual void OnException(ExceptionContext filterContext)
            {
                if (filterContext == null)
                {
                    throw new ArgumentNullException("filterContext");
                }
                if (!filterContext.IsChildAction && (!filterContext.ExceptionHandled && filterContext.HttpContext.IsCustomErrorEnabled))
                {
                    Exception innerException = filterContext.Exception;
                    if ((new HttpException(null, innerException).GetHttpCode() == 500) && this.ExceptionType.IsInstanceOfType(innerException))
                    {
                        string controllerName = (string) filterContext.RouteData.Values["controller"];
                        string actionName = (string) filterContext.RouteData.Values["action"];
                        HandleErrorInfo model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
                        ViewResult result = new ViewResult {
                            ViewName = this.View,
                            MasterName = this.Master,
                            ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                            TempData = filterContext.Controller.TempData
                        };
                        filterContext.Result = result;
                        filterContext.ExceptionHandled = true;
                        filterContext.HttpContext.Response.Clear();
                        filterContext.HttpContext.Response.StatusCode = 500;
                        filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
                    }
                }
            }

     

    这些代码的思路是这样的

    HandleErrorAttribute-->HandleErrorInfo(model)-->ViewResult-->{ViewName:Error}

    即 抛出一个Share文件夹里的Error视图。

     

    可以看出以Filter形式给出的,说明它有能力以特性的形式‘贴’在控制器Controller或Action上,让代码更为‘优美’!因为Filter微软也给我们留了‘FilterConfig’入口,可以全局性的‘注入’

     

    至此,我们可以看出,有至少有两种形式来规划的我的‘异常’,一是以特性的形式‘贴’在Controller和Action上,二是,以全局的‘FilterConfig’注册。不管怎么样,结果只有一个,try...catch...离我们渐渐远去了,不是吗?那我们继续看下去!

     

    异常通过以'里'向'外'抛出去的,就像我们常常看到的异常黄页,那个是抛到最外面的一个异常页面。

    1.Action-->HandleErrorAttribute(默认)-->IExceptionFilter-->OnException

    2.Controller-->HandleErrorAttribute(默认)-->IExceptionFilter-->OnException

     

    二、自定义我们的异常类


    在自定义我们的异常辅助类前,我们首先明确的几点:

    1.全局的异常用CustomExceptionAttribute自定义异常类来捕获。

    2.对于Action是JsonResult的用Json格式的‘事件’用‘AsyncExceptionAttribute’异常来捕获。

     

    顺序是:Action-->AsyncExceptionAttribute-->CustomExceptionAttribute-->HandleErrorAttribute(默认)-->IExceptionFilter-->OnException

    在Action里发生异常,如果是JsonResult,用AsyncExceptionAttribute来捕获出去,然后进行全局的CustomExceptionAttribute

    当设定 filterContext.ExceptionHandled=True时 表示此异常’已被处理‘,反之异常在后续的捕获机制中向外抛出,这个由自己的需要自由订制。

    1)AsyncExceptionAttribute定义:

     

    如果是JsonResult异常的话,我们进行处理组合成一个Data,状态为success=false,message='异常信息'。

    这里没有做filterContext.ExceptionHandled=True处理,为了让异常向’外抛‘CustomExceptionAttribute 处理,因为我们要记录这个异常日志,而不是仅仅的显示给UI界面。

     

    2)CustomExceptionAttribute全局异常:

     public override void OnException(ExceptionContext filterContext)
            {
    
                int StatusCode = filterContext.HttpContext.Response.StatusCode;
    
                if (filterContext.Exception != null && StatusCode != 404)
                {
    
                    //写入日志 记录
                    string message = string.Format("------------------------------------------------------------------------------------------------------------------------------------------------------------\r\n下面是捕获的异常信息摘要:\r\n 时间:{0}\r\n消息类型:{1}\r\n 消息内容:{2}\r\n 引发异常的方法:{3}\r\n 引发异常源:{4}"
                         , DateTime.Now
                         , filterContext.Exception.GetType().Name
                         , filterContext.Exception.Message
                         , filterContext.Exception.TargetSite
                         , filterContext.Exception.Source + filterContext.Exception.StackTrace
                         );
    
                    DbEntityValidationException e = filterContext.Exception as DbEntityValidationException;
    
                    if (e != null && e.EntityValidationErrors.Count() > 0)
                    {
                        System.Text.StringBuilder tempDate = new StringBuilder("\r\n下面是捕获的验证类详细信息:\r\n");
    
                        foreach (var eve in e.EntityValidationErrors)
                        {
                            tempDate.AppendLine(string.Format("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:",
               eve.Entry.GetType().Name, eve.Entry.State));
    
                            foreach (var ve in eve.ValidationErrors)
                            {
                                tempDate.AppendLine(string.Format("- Property: \"{0}\", Error: \"{1}\"",
                                    ve.PropertyName, ve.ErrorMessage));
                            }
                        }
    
                        message += tempDate.Append("\r\n").ToString();
                    }
    
                    Task t = new Task(() =>
                    {
                        WL.Common.SysLogHelp.WriteLogFile("ErrorLog", message, filterContext.HttpContext);
                    });
    
                    t.Start();
                    //t.Wait();
                }
    
                if (filterContext.Result is JsonResult)
                    filterContext.ExceptionHandled = true;
                else
                    base.OnException(filterContext);
    
            }

     

    CustomExceptionAttribute全局异常定交方法,首先排除不是404的异常,因为,404可能是死链,网站不应该处理,交给IIS级的异常程程序处理。

    IIS配置管理员(windows 2012)

     

     IIS 配置管理器里有两个’错误‘处理机制,在.NET配选项里配置404页,有个一潜在的问题StatusCode=304而不是404,微软也有相关的说明,而在IIS错误页里配置的话没有问题!

    <customErrors mode="On">

    //在此节里配置不会得到正确的404 StateCode
    </customErrors>

     

    提倡的应该在 HttpErrors节点配置

    <httpErrors errorMode="Custom" existingResponse="Replace">
    <clear/>
    <error statusCode="404" path="ErrorPages\404.html"/>
    <error statusCode="500" path="ErrorPages\500.html"/>
    </httpErrors>

     

     再看看打印出的404页面状态码

     

    这次就正确了,所以404页要交给IIS处理,我们上述代码没有处理404页,如果处理的话,那么可能得不到StatusCode=404的状态码,表面上UI没有任何问题,但对于SEO而言,死链得不到及时处理。

    话题转回来,继续我们的文章。

     

    同时上面的代码对于filterContext.Result is JsonResult 设置filterContext.ExceptionHandled = true,而对于其它的异常base.OnException(filterContext),继续向外面处理,防止有其它异常不能及时处理。我们用了Task任务异步来写入日志,写日志毕竟也是浪费时间的嘛。

    现在我们测试下,这两种异常吧,看看代码是不是’优美‘了许多

    前台Test Code:

     

    后台Test Code:

     

     

    测试结果:

    UI层捕获显示

     

    Log txt Exception:

     

     

     再看看,其它的测试方式?

     正常test:

     

     

    修改下前台code,后台代码不变,进行异常test:

    全局捕获的异常日志:

     

     

    对比以前的try...catch...话,明显前者漂亮了许多。

     

    小结:在传统的web form 框架里对于集成AOP思想,是一个较操心的事情,微软也看到了这点,新的mvc框架确实比以前改进了很多,为我们对于代码的精化及设计思路上更进了一步,诸如异常捕获,权限验证等也能很好的体现出来。如果你还是以前的程序猿,现在也算体会到了设计师的畅快感,不是吗?

     

    作者: 谷歌's谷歌's博客园
    出处: http://www.cnblogs.com/laogu2/ 欢迎转载,但任何转载必须保留完整文章,在显要地方显示署名以及原文链接。如您有任何疑问或者授权方面的协商,请 给我留言
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-4 12:48 , Processed in 0.061264 second(s), 27 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表