如何解决EnterLib异常处理框架最大的局限——基于异常"类型"的异常处理策略

个人觉得EnterLib的EHAB(Exception Handling
Application
Block)是一个不错的异常处理框架,借助于EHAB,我们可以配置的方式来自定义异常处理策略,从而带来最大的灵活性和可维护性。但是,在我看来,EHAB有一个最大的局限,把就是异常处理策略的粒度过大——只能提供基于异常类型级别。本篇文章通过一个自定义ExceptionHandler很好地解决了这个问题。

一、EnterLib基于异常类型的异常处理策略

EnterLib的异常处理策略基本上可以通过这样的的公式来表示:Exception Policy = Exception Type + Exception Handlers + Post Handling Action,它表达的意思是:“对于某种类型的异常,应该采用哪些Exception Handler去处理,而被处理后的异常还需要采用怎样的后续操作(将异常吃掉、或者是重新抛出)”。

也就是说,抛出类型的异常类型决定了最终采取的处理策略,这在大部分情况下是可以接受的。但是在很多场景中,不同情况下也可以抛出相同类型的异常,我们期望的行为是:尽管异常类型一样,我们也可以根据具体抛出的异常定义不同的异常处理策略。

一个最为典型的场景就是基于数据库的数据存取,如果你采用的SQL
Server,抛出的异常永远只有一种:SqlException。如果完全按照EnterLib
EHAB的做法,在任何情况下抛出的SqlException对应的处理方式都是一样的。但是抛出SqlException的情况非常多,比如Server连接断开、认证失败、数据库对象不存在、违反一致性约束等等,如果异常处理框架能够根据最终抛出的异常的具体属性,“智能”地应用相应的策略去处理,这才是我们乐于看到的。

二、一个特殊的ExceptionHandler——FilterableHandler

为了解决这个问题,我创建了一个特殊的Exception Handler,我将它起名为FilterableHandler。说它特别,是因为FilterableHandler并不从事具体的异常处理操作(比如异常封装、替换、日志等),而是为某个具体的异常类型重新定义了异常处理策略。

实际上,我在很早之前就定义了一个相似的FilterableHandler,有兴趣的话可以参考《创建一个自定义Exception Handler改变ELAB的异常处理机制》。由于在最新的EnterLib中,底层的实现机制发生了根本性的改变,这个ExceptionHandler已经不能在使用。所以我对其进行的了修正,同时根据可扩展性进行重新设计。

之所以称这个ExceptionHandler为FilterableHandler,是在于它具有对抛出的异常具有“筛选”的功能。说得具体点,FilterableHandler将抛出的异常对象,传入一组具有筛选功能的ExceptionHandler列表,我个人将这个列表命名为FiterableExceptionHandlerPipeline。FiterableExceptionHandlerPipeline对象包含一个筛选器和一组ExceptionHandler,如果传入的异常通过了筛选器的筛选,该异常最终被分发到相应的ExceptionHandler列表中进行处理。FiterableExceptionHandlerPipeline大概的定义如下:

   1: public class FilterableHandlerPipeline
   2: {
   3:     public IFilter Filter { get; private set; }
   4:     public IEnumerable<IExceptionHandler> ExceptionHandlers { get; private set; }
   5:  
   6:     public FilterableHandlerPipeline(IFilter filter, IEnumerable<IExceptionHandler> exceptionHandlers)
   7:     {
   8:         Guard.ArgumentNotNull(filter, "filter");
   9:         Guard.ArgumentNotNull(exceptionHandlers, "exceptionHandlers");
  10:         this.Filter = filter;
  11:         this.ExceptionHandlers = exceptionHandlers;
  12:     }
  13: }

而IFilter接口在更为简单,仅仅具有如下一个唯一的Match方法。布尔类型的返回值表明是否和指定的异常相匹配,当返回值为True的时候,FiterableExceptionHandlerPipeline采用用自己的ExceptionHandler列表去处理抛出的异常,否则就直接忽略掉。

   1: public interface IFilter
   2: {
   3:     bool Match(Exception ex);
   4: }

你可以从下面给出的关于FilterableHandler的完整的代码去分析具体的异常处理实现原理。而实际上,最为复杂的不是FilterableHandler本身的实现,而是与之相关的配置元素的定义。由于这会涉及到很多关于EnterLib底层和Unity相关的知识点,不是三言两语就能讲明白的,所以在这里就不对FilterableHandler的配置体系作介绍了,有兴趣的话可以通过这里直接下载源代码。

   1: [ConfigurationElementType(typeof(FilterableHandlerData))]
   2: public class FilterableHandler:IExceptionHandler
   3: {
   4:     public IEnumerable<FilterableHandlerPipeline> FilterableHandlerPipelines { get; private set; }
   5:     public IEnumerable<IExceptionHandler> DefaultHandlers { get; private set; }
   6:  
   7:     public FilterableHandler(IEnumerable<FilterableHandlerPipeline> filterableHandlerPipelines, IEnumerable<IExceptionHandler> defaultHandlers)
   8:     {
   9:         Guard.ArgumentNotNull(defaultHandlers, "defaultHandlers");
  10:         filterableHandlerPipelines = filterableHandlerPipelines ?? new List<FilterableHandlerPipeline>();
  11:         this.FilterableHandlerPipelines = filterableHandlerPipelines;
  12:         this.DefaultHandlers = defaultHandlers;
  13:     }
  14:     public Exception HandleException(Exception exception, Guid handlingInstanceId)
  15:     {
  16:         Guard.ArgumentNotNull(exception,"exception");
  17:         var handlerPipeline = (from pipeline in this.FilterableHandlerPipelines
  18:                                where pipeline.Filter.Match(exception)
  19:                                select pipeline).FirstOrDefault();
  20:         if (null != handlerPipeline)
  21:         {
  22:             return ExecuteHandlerChain(exception, handlingInstanceId, handlerPipeline.ExceptionHandlers);
  23:         }
  24:         else
  25:         {
  26:             return ExecuteHandlerChain(exception, handlingInstanceId, DefaultHandlers);
  27:         }
  28:     }
  29:  
  30:     private static Exception ExecuteHandlerChain(Exception exception, Guid handlingInstanceId, IEnumerable<IExceptionHandler> handlers)
  31:     {
  32:         var lastHandlerName = String.Empty;
  33:         try
  34:         {
  35:             foreach (var handler in handlers)
  36:             {
  37:                 lastHandlerName = handler.GetType().Name;
  38:                 exception = handler.HandleException(exception, handlingInstanceId);
  39:             }
  40:         }
  41:         catch (Exception ex)
  42:         {
  43:             var errorMsg = string.Format("Unable to handle the exception: {0}", lastHandlerName);
  44:             throw new ExceptionHandlingException(errorMsg, ex);
  45:         }
  46:         return exception;
  47:     }
  48: }

三、通过FilterableHandler对SqlException进行针对性处理

我现在通过一个简单的例子来演示FilterableHandler如何使用(源代码从这里下载),我们使用的场景就是上面提到过的对SqlException的针对性处理。根据SqlException抛出的场景,本例将起分为三种类型:

  • 系统异常:基于SQL Server自身抛出的异常,我们将异常编号,即SqlException的Number小于50000的称为系统异常;
  • 业务异常:编程人员根在编写SQL脚本的时候,根据相应的业务逻辑,通过调用RAISERROR语句手工抛出的异常。在默认情况下这种异常的编号为50000;
  • 其他:任何编号高于50000的异常。

注:关于RAIERROR语句以及SQL Server异常处理相关的内容,你可以参阅我下面三篇文章:

谈谈基于SQL Server的Exception Handling - PART I

谈谈基于SQL Server 的Exception Handling - PART II

谈谈基于SQL Server 的Exception Handling - PART III

为了对SqlException进行针对处理,我们对抛出的SqlException进行封装。对应于上述三种类型,我定义如如下三种异常:SqlSystemException、SqlBusinessException和DbException。

   1: namespace Artech.ExceptionHandling.Demo
   2: {
   3:     [Serializable]
   4:     public class SqlSystemException : Exception
   5:     {
   6:         public SqlSystemException() { }
   7:         public SqlSystemException(string message) : base(message) { }
   8:         public SqlSystemException(string message, Exception inner) : base(message, inner) { }
   9:         protected SqlSystemException(
  10:           System.Runtime.Serialization.SerializationInfo info,
  11:           System.Runtime.Serialization.StreamingContext context)
  12:             : base(info, context) { }
  13:     }
  14:  
  15:     [Serializable]
  16:     public class SqlBusinessException : Exception
  17:     {
  18:         public SqlBusinessException() { }
  19:         public SqlBusinessException(string message) : base(message) { }
  20:         public SqlBusinessException(string message, Exception inner) : base(message, inner) { }
  21:         protected SqlBusinessException(
  22:           System.Runtime.Serialization.SerializationInfo info,
  23:           System.Runtime.Serialization.StreamingContext context)
  24:             : base(info, context) { }
  25:     }
  26:  
  27:     [Serializable]
  28:     public class DbException : Exception
  29:     {
  30:         public DbException() { }
  31:         public DbException(string message) : base(message) { }
  32:         public DbException(string message, Exception inner) : base(message, inner) { }
  33:         protected DbException(
  34:           System.Runtime.Serialization.SerializationInfo info,
  35:           System.Runtime.Serialization.StreamingContext context)
  36:             : base(info, context) { }
  37:     }
  38: }

我们需要作的进行通过配置定义处理SqlException的处理策略,整个配置定义在如下的代码片断中。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:    ... ...
   4:     <exceptionHandling>
   5:         <exceptionPolicies>
   6:             <add name="Policy">
   7:                 <exceptionTypes>
   8:                     <add name="All Exceptions" type="System.Data.SqlClient.SqlException, System.Data, Version=4.0.0.0, 
   9: Culture=neutral, PublicKeyToken=b77a5c561934e089"
  10:                         postHandlingAction="ThrowNewException">
  11:                         <exceptionHandlers>
  12:                           <add name="filterHandler" type="Artech.ExceptionHandling.FilterableHandler, Artech.ExceptionHandling.Lib">
  13:                             <default>
  14:                               <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, 
  15: Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
  16:                                 exceptionMessage="Business Error..." wrapExceptionType="Artech.ExceptionHandling.Demo.DbException, Artech.ExceptionHandling.Demo" />
  17:                             </default>
  18:                             <filters>
  19:                               <add name="businessError" type="Artech.ExceptionHandling.PropertyValueEquivalencePipeline, Artech.ExceptionHandling.Lib" 
  20: property="Number" value="50000">
  21:                                 <exceptionHandlers>
  22:                                   <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, 
  23: Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
  24:                                exceptionMessage="Busiess Error..." wrapExceptionType="Artech.ExceptionHandling.Demo.SqlBusinessException, Artech.ExceptionHandling.Demo" />
  25:                                 </exceptionHandlers>
  26:                               </add>
  27:                               <add name="systemError" type="Artech.ExceptionHandling.PropertyValueRangePipeline, Artech.ExceptionHandling.Lib" property="Number"  
  28: upperBound="50000" upperRangeBoundType="Exclusive">
  29:                                 <exceptionHandlers>
  30:                                   <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, 
  31: Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
  32:                                exceptionMessage="System Error..." wrapExceptionType="Artech.ExceptionHandling.Demo.SqlSystemException, 
  33: Artech.ExceptionHandling.Demo" />
  34:                                 </exceptionHandlers>
  35:                               </add>
  36:                             </filters>
  37:                           </add>                            
  38:                         </exceptionHandlers>
  39:                     </add>
  40:                 </exceptionTypes>
  41:             </add>
  42:         </exceptionPolicies>
  43:     </exceptionHandling>
  44: </configuration>

虽然配置稍微复杂了一点,但是结构还算是很清楚的。我们将FilterableHandler作为处理SqlException的唯一的ExceptionHandler。而FilterableHandler整个配置包含如下两个部分<default>和<filters>。<filters>自然就是定义的一组筛选分支,而<default>则是定义了一个后备——如果抛出的异常满足所有的筛选分支,则通过定义在<default>中的ExceptionHandler列表进行才处理。

   1: <add name="filterHandler" type="Artech.ExceptionHandling.FilterableHandler, Artech.ExceptionHandling.Lib">
   2:   <default>
   3:     <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
   4:       exceptionMessage="Business Error..." wrapExceptionType="Artech.ExceptionHandling.Demo.DbException, Artech.ExceptionHandling.Demo" />
   5:   </default>
   6:   <filters>
   7:     <add name="businessError" type="Artech.ExceptionHandling.PropertyValueEquivalencePipeline, Artech.ExceptionHandling.Lib" property="Number" value="50000">
   8:       <exceptionHandlers>
   9:         <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, 
  10: Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
  11:      exceptionMessage="Bar" wrapExceptionType="Artech.ExceptionHandling.Demo.SqlBusinessException, Artech.ExceptionHandling.Demo" />
  12:       </exceptionHandlers>
  13:     </add>
  14:     <add name="systemError" type="Artech.ExceptionHandling.PropertyValueRangePipeline, Artech.ExceptionHandling.Lib" 
  15: property="Number"  upperBound="50000" upperRangeBoundType="Exclusive">
  16:       <exceptionHandlers>
  17:         <add name="Wrap Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.WrapHandler, 
  18: Microsoft.Practices.EnterpriseLibrary.ExceptionHandling"
  19:      exceptionMessage="System Error..." wrapExceptionType="Artech.ExceptionHandling.Demo.SqlSystemException, Artech.ExceptionHandling.Demo" />
  20:       </exceptionHandlers>
  21:     </add>
  22:   </filters>
  23: </add>   

在<filters>中定义了两个FilterableHandlerPipeline:PropertyValueEquivalencePipeline和PropertyValueRangePipeline。PropertyValueEquivalencePipeline的筛选器根据抛出异常的某个属性的值是否等于指定的值进行筛选,而PropertyValueRangePipeline的筛选器则根据抛出异常的某个属性值是否在指定的范围内进行筛选。在这里用作筛选的属性名名称为Number,PropertyValueRangePipeline指定的上限为5000,upperRangeBoundType为“Exclusive”表示包含此上限,并且没有指定下限,所以这里的筛选逻辑就Number<50000。而PropertyValueEquivalencePipeline通过value属性设置成50000,表明它需要筛选Number=50000的异常。

<filters>下的两个筛选元素,以及<default>节点下的ExceptionHandler列表包含一个EnterLib提供的WrapHandler,对抛出的异常进行封装,在这里我们指定了不同的封装异常类型:SqlBusinessException、SqlSystemException和DbException。

我们验证上面定义的异常处理策略,看看抛出的SqlException是否按照我们的预期进行了相应的封装,我现定义了如下一个辅助方法:HandleException。

   1: private static void HandleException(Action task)
   2: {
   3:     try
   4:     {
   5:         try
   6:         {
   7:             task();
   8:         }
   9:         catch (SqlException ex)
  10:         {
  11:             if (ExceptionPolicy.HandleException(ex, "Policy"))
  12:             {
  13:                 throw;
  14:             }
  15:         }
  16:     }
  17:     catch (Exception ex)
  18:     {
  19:         Console.WriteLine(ex.GetType().FullName);
  20:     }
  21: }

现在我们分三种情况调用这个辅助方法:

1、创建一个数据库连接,但是指定一个错误的密码,当我们开启连接的时候,系统会自动抛出一个SqlException,这个异常应该被封装成SqlSystemException;

2、通过创建一个DbCommand,执行RAISERROR语句,并指定相应的出错信息、错误严重级别(Serveriry)和状态(State),这个异常应该被封装成SqlBusinessException(Number=50000);

3、通过创建一个DbCommand,执行RAISERROR语句,指定一个MessageId(通过调用系统存储过程sp_addmessage创建,该值会转换成SqlException的Number),这个异常应该被封装成DbException

   1: HandleException(
   2:         () =>
   3:         {
   4:             var connstring = "Server=.; Database=TestDb; Uid=sa; Pwd=invalidPwd";
   5:             var conection = new SqlConnection(connstring);
   6:             conection.Open();
   7:         });
   8:  
   9: HandleException(
  10:          () =>
  11:          {
  12:              var connstring = "Server=.; Database=TestDb; Uid=sa; Pwd=password";
  13:              var conection = new SqlConnection(connstring);
  14:              var command = conection.CreateCommand();
  15:              command.CommandText = "RAISERROR ('The order record does not exist',16,1)";
  16:              conection.Open();
  17:              command.ExecuteNonQuery();
  18:          });
  19:  
  20: HandleException(
  21:          () =>
  22:          {
  23:              var connstring = "Server=.; Database=TestDb; Uid=sa; Pwd=password";
  24:              var conection = new SqlConnection(connstring);
  25:              var command = conection.CreateCommand();
  26:              command.CommandText = "RAISERROR (50001,16,1)";
  27:              conection.Open();
  28:              command.ExecuteNonQuery();
  29:          });

以下的输出和我们的预期完全一致:

   1: Artech.ExceptionHandling.Demo.SqlSystemException
   2: Artech.ExceptionHandling.Demo.SqlBusinessException
   3: Artech.ExceptionHandling.Demo.DbException

四、FiterableHandler的可扩展性

FilterableHandler的核心在于有一组具有不同筛选器的FiterableExceptionHandlerPipeline。我默认定义了两个基于属性比较的FiterableExceptionHandlerPipeline,即PropertyValueEquivalencePipeline和PropertyValueRangePipeline。实际上你可以通过继承FiterableExceptionHandlerPipeline基类,实现你自定义的筛选方式。

作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文链接

时间: 2024-09-20 01:14:01

如何解决EnterLib异常处理框架最大的局限——基于异常"类型"的异常处理策略的相关文章

一个用于J2EE应用程序的异常处理框架

在大多数Java项目中,大部分代码都是样板代码.异常处理就属于此类代码.即使业务逻辑只有3到4行代码,用于异常处理的代码也要占10到20行.本文将讨论如何让异常处理保持简单和直观,使开发人员可以专心于开发业务逻辑,而不是把时间浪费在编写异常处理的样板代码上.本文还将说明用于在J2EE环境中创建和处理异常的基础知识和指导原则,并提出了一些可以使用异常解决的业务问题.本文将使用Struts框架作为表示实现,但该方法适用于任何表示实现. 使用checked和unchecked异常的场景 您是否曾经想过

异常以及异常处理框架探析

概述 一般情况下,企业级应用都对应着复杂的业务逻辑,为了保证系统的健壮,必然需要面对各种系统业务异常 和运行时异常. 不好的异常处理方式容易造成应用程序逻辑混乱,脆弱而难于管理.应用程序中充斥着零散的异常 处理代码,使程序代码晦涩难懂.可读性差,并且难于维护. 一个好的异常处理框架能为应用程序的异常处理提供 统一的处理视图,把异常处理从程序正常运行逻辑分离出来,以至于提供更加结构化以及可读性的程序架构.另外,一个好 的异常处理框架具备可扩展性,很容易根据具体的异常处理需求,扩展出特定的异常处理逻

WCF技术剖析之二十二: 深入剖析WCF底层异常处理框架实现原理[下篇]

WCF客户端和服务端的框架体系相互协作,使得开发人员可以按照我们熟悉的方式进行异常的处理:在服务操作执行过程中抛出异常(FaultException),在调用服务时捕获异常,完全感觉不到"分布式"的存在,如同典型的"本地"操作一般.为了实现这样的效果,WCF在内部为我们作了很多. 消息交换是WCF进行通信的唯一手段,消息不仅仅是正常服务调用请求和回复的载体,服务端抛出的异常,甚至是服务的元数据都是通过消息的形式传向客户端的.所以,实现异常与消息之间的转换是整个异常处

在Spring基础上实现自己的异常处理框架

该异常处理框架满足的要求: 完整的异常组织结构 异常的统一处理 可配置,受管式,方便使用 完整的异常组织结构: 用户可以方便的定义自己的异常,但所有UncheckedException需要继承BaseAppRuntimeException,所有的checked Exception可以继承BaseAppException,或者需要抛出且不需要check时用WrapperredAppException封装后抛出 合理地使用checked异常 Exception有唯一的error code,这样用户报

Java异常以及异常处理框架探析

一般情况下,企业级应用都对应着复杂的业务逻辑,为了保证系统的健壮,必然需要面对各种系统业务异常和运行时异常. 不好的异常处理方式容易造成应用程序逻辑混乱,脆弱而难于管理.应用程序中充斥着零散的异常处理代码,使程序代码晦涩难懂.可读性差,并且难于维护. 一个好的异常处理框架能为应用程序的异常处理提供统一的处理视图,把异常处理从程序正常运行逻辑分离出来,以至于提供更加结构化以及可读性的程序架构.另外,一个好的异常处理框架具备可扩展性,很容易根据具体的异常处理需求,扩展出特定的异常处理逻辑. 另外,异

[原创].NET 业务框架开发实战之八 业务层Mapping的选择策略

原文:[原创].NET 业务框架开发实战之八 业务层Mapping的选择策略 .NET 业务框架开发实战之八 业务层Mapping的选择策略 前言:在上一篇文章中提到了mapping,感觉很像在重新实现NHibernate.其实文章的本意是想反映出Richard在思考的时候的一些选择:利用现有的,还是最后自己用别的方式实现.如果一上来就说什么什么好,那太武断了,也很片面,系列文章反复的在强调一点:技术有它的适用场景,没有完美的技术.很多的朋友说本系列在近似的开发一个ORM,其实不是:ORM就是把

JAVA【异常二】异常处理机制

Java中异常提供了一种识别及响应错误情况的一致性机制,有效地异常处理能使程序更加健壮.易于调试.异常之所以是一种强大的调试手段,在于其回答了以下三个问题: 什么出了错? 在哪出的错? 为什么出错? 在有效使用异常的情况下,异常类型回答了"什么"被抛出,异常堆栈跟踪回答了"在哪"抛出,异常信息回答了"为什么"会抛出.   在Java 应用程序中,异常处理机制为:抛出异常,捕捉异常. 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运

ssh-SSH框架下 jsp页面提交file类型input404 求高手指导

问题描述 SSH框架下 jsp页面提交file类型input404 求高手指导 从页面进入action,名称路径全对,form提交的时候如果file类型的input标 签不选择文件可以找到并进入后台,如果选择文件了则是404.这是什么原因? 求大神给予解答!!!谢谢了!!! 解决方案 这个最好分开做处理,写程序之前先想好流程,别急着写代码 解决方案二: 分析下你的action定义的请求是get的还是post. 最好贴上具体代码!

c# 串口读取-读取串口数据添加了一个TimeoutException异常处理方法,出现了dll异常

问题描述 读取串口数据添加了一个TimeoutException异常处理方法,出现了dll异常 报错信息: 在 System.TimeoutException中第一次偶然出现的"System.dll"类型异常. 之后程序就不在接收数据了,也不出现程序报错,必须重新启动程序,才能继续接收数据. 错误一般会在程序运行1天之后出现假死现象.是不是因为我只是把错误信息记下来了,并没有进行错误处理?我的逻辑是,错误之后,丢弃这一组数据,然后重新进行数据接收. 跪求,本人无法解觉了现在. 相关代码