如何在EHAB(EntLib)中定义”细粒度”异常策略?

为了解决EntLib的EHAB(Exception Handling Application Block)只能在异常类型级别控制异常处理策略的局限,我在很久之前曾经自定义了一个特殊的异常处理器来提供“细粒度”异常策略的定义(《如何解决EnterLib异常处理框架最大的局限》)。我个人觉得具有一定的实用价值,今天特意对其进行了重构,并将其放到了我在CodePlex上新创建的项目EntLib Extensions

目录 一、完全基于类型的异常策略 二、通过FilterableHandler定义细粒度的异常策略 三、基于“异常筛选”的异常策略 四、异常筛选的匹配优先级

一、完全基于类型的异常策略

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

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

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

二、通过FilterableHandler定义细粒度的异常策略

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

由于FilterableHandler本质上就是一个Exception Handler,所以它所提供细粒度异常策略完全定义在基于这个Exception Handler的配置中。为了上读者对“细粒度异常控制”在FilterableHandler德支持有个初步的了解,我们可以来大体了解一下FilterableHandler的配置结构。

   1: <filters>
   2:    <add name="filter1" type="filterType, assemblyName" .../>
   3:    <add name="filter2" type="filterType, assemblyName" .../>
   4: </filters>
   5: <filterTable>
   6:    <add name="filterEntry1" filter="filter1">
   7:        <exceptionHandlers>
   8:           <add name="handler1" type="exceptionHandlerType, assemblyName" .../>
   9:           <add name="handler2" type="exceptionHandlerType, assemblyName" .../>
  10:           <add name="handler3" type="exceptionHandlerType, assemblyName" ...>
  11:        </exceptionHandlers>
  12:    </add>
  13:    
  14:    <add name="filterEntry2" filter="filter2">
  15:        <exceptionHandlers>
  16:           <add name="handler4" type="exceptionHandlerType, assemblyName" .../>
  17:           <add name="handler5" type="exceptionHandlerType, assemblyName" .../>
  18:           <add name="handler6" type="exceptionHandlerType, assemblyName" .../>
  19:        </exceptionHandlers>
  20:    </add>
  21: </filterTable>

从上面给出的配置中,我们可以大体可以看出:针对某个异常的异常策略被分为两个分支,每个分支分别通过对应着<filterTable>结点下定义的两个“筛选器体条目”(filterEntry1和filterEntry2)。而这两个筛选器表分别适用配置的“异常筛选器”filter1和filter2来判断处理的异常是否满足当前分支的条件。当捕获的异常满足相应的分支的筛选条件,则通过当前分支定义的异常处理器列表(第一个分支:handler1、handler2和handler3,第二个分支:handler4、handler5和handler6)。

三、基于“异常筛选”的异常策略

实际上FilterableHandler提供的“细粒度”异常策略是通过“异常筛选”机制实现的。由于我们需要根据捕获的异常来决定应该采用那个分支对其进行处理,而用于分支判断通过一个特殊的组件——异常筛选器(ExceptionFilter)来实现。我们为异常筛选器定义了如下一个简单的接口,唯一的方法Match用于判断给定的Exception对象是否满足当前异常筛选器的筛选条件。

   1: public interface IExceptionHandler
   2: {
   3:    bool Match(Exception exception)
   4: }

我们可以通过实现这个接口来自定义我们需要的异常筛选器,比如我们定义了如下一个DomainFilter。该DomainFilter根据Exception对象某个指定的属性值是否和在预先指定的指列表中,进而判断异常是否满足筛选条件。

   1: public class DomainFilter: IExceptionFilter
   2: {
   3:     public string PropertyName{get; set}
   4:     public string Values{get;set;}
   5:  
   6:     public DomainFilter(string propertyName, string values)
   7:     {
   8:         this.PropertyName = propertyName;
   9:         this.Values = values;
  10:     }
  11:     
  12:     public bool Match(Exception exception)
  13:     {
  14:        var property = exception.GetType().GetProperty(this.PropertyName);
  15:        var value    = property.GetValue(exception, null);
  16:        return this.Values.Split(',').Contains(value.ToString());
  17:     }
  18: }

比如说,现在我们对某种类型的异常进行处理,并且该异常类型具有一个Number属性。现在我们需要针对异常的这个属性来进行分支的选择,比如Number=1、2、和3采用hander1、hander2和handler3进行处理,Number=4、5和6则采用hander4、hander5和handler6进行处理。相应的配置结构如下:

   1: <filters>
   2:    <add name="domainFilter1" type="DomainFilter,assemblyName" property="Number" values="1,2,3" />
   3:    <add name="domainFilter2" type="DomainFilter, assemblyName" property="Number" values="4,5,6" />
   4: </filters>
   5: <filterTable>
   6:    <add name="filterEntry1" filter="domainFilter1">
   7:        <exceptionHandlers>
   8:           <add name="handler1" type="exceptionHandlerType, assemblyName" .../>
   9:           <add name="handler2" type="exceptionHandlerType, assemblyName" .../>
  10:           <add name="handler3" type="exceptionHandlerType, assemblyName" ...>
  11:        </exceptionHandlers>
  12:    </add>
  13:    
  14:    <add name="filterEntry2" filter="domainFilter2">
  15:        <exceptionHandlers>
  16:           <add name="handler4" type="exceptionHandlerType, assemblyName" .../>
  17:           <add name="handler5" type="exceptionHandlerType, assemblyName" .../>
  18:           <add name="handler6" type="exceptionHandlerType, assemblyName" .../>
  19:        </exceptionHandlers>
  20:    </add>
  21: </filterTable>

四、异常筛选的匹配优先级

实际上,这个“异常筛选”机制是根据WCF 4.0的新特性——路由服务的“消息筛选”机制来设计。路由服务具有一个“匹配优先级”的特性,我依然将其借用过来。对于根据配置的异常筛选器决定的异常处理分支,在某个情况下可以出现这样的情况:处理的异常同时满足多个分支的筛选条件。为此在定义筛选表中的每一个筛选器条目(ExceptionFilterEntry)中除了指定异常筛选器的配置名称外,还具有一个类型为整形的priority属性表示匹配的级别。数字越大,级别越高,只有匹配的级别最高的分支会被选用。

   1: <filterTable>
   2:   <add name="filterEntry1" filter="filter1" priority="2">
   3:      <exceptionHandlers>...</exceptionHandlers>
   4:   </add>
   5:   <add name="filterEntry2" filter="filter2" priority="1">
   6:      <exceptionHandlers>...</exceptionHandlers>
   7:   </add>
   8:  
   9:   <add name="filterEntry3" filter="filter3" priority="0">
  10:      <exceptionHandlers>...</exceptionHandlers>
  11:   </add>
  12:  
  13: </filterTable>

比如对于上面的配置,如果filterEntry2和filterEntry3都满足匹配条件,那么会选择优先级别最高的filterEntry2(priority="1"),而放弃级别较低的filterEntry1。如果最高级别的匹配分支有多个,则会抛出ConfigurationErrorsException。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏专注 Java 基础分享

虚拟机类加载机制

虚拟机把字节码文件从磁盘加载进内存的这个过程,我们可以粗糙的称之为「类加载」,因为「类加载」不仅仅是读取一段字节码文件那么简单,虚拟机还要进行必要的「验证」、「...

4657
来自专栏JavaEdge

JVM内存模型1 程序计数器2. Java虚拟机栈(JVM Stack)3. 本地方法栈(Native Method Stack)4 Java堆(Java Heap)5 方法区6 直接内存(Direc

4309
来自专栏纯洁的微笑

jvm系列(一):java类的加载机制

1、什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Cl...

3846
来自专栏偏前端工程师的驿站

Java魔法堂:类加载机制入了个门

一、前言                                 当在CMD/SHELL中输入 $ java Main<CR><LF> 后,Main程序...

2037
来自专栏Java技术栈

设计模式之动态代理模式实战

昨天分享了静态代理的概念及存在的缺点,所以今天讲的动态代理模式十分重要。动态代理在我们工作当中应用相当广泛,如Srping AOP就是动态代理的在开源框架的比较...

3494
来自专栏韩伟的专栏

框架设计原则和规范(完)

祝大家圣诞节快乐!有事没事别出门,外面太!挤!了! 此文是《.NET:框架设计原则、规范》的读书笔记,本文内容较多,共分九章,今天推送最后一章。 1. 什么是好...

2874
来自专栏IMWeb前端团队

Promise接口实现之jQuery 的deferred对象

本文作者:IMWeb json 原文出处:IMWeb社区 未经同意,禁止转载 Promise是什么? 我们知道JavaScript是单线程,如果遇到某...

18410
来自专栏二进制文集

JDK源码分析 多线程

对于JDK源码分析的文章,仅仅记录我认为重要的地方。源码的细节实在太多,不可能面面俱到地写清每个逻辑。所以我的JDK源码分析,着重在JDK的体系架构层面,具体源...

1022
来自专栏向治洪

java基础Haep(堆)和Stack(栈)区别

简单的可以理解为: heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。  stack:是自动分配变量,以及函数调用的时候所使用的一些空...

2097
来自专栏皮皮之路

【JVM】浅谈双亲委派和破坏双亲委派

笔者曾经阅读过周志明的《深入理解Java虚拟机》这本书,阅读完后自以为对jvm有了一定的了解,然而当真正碰到问题的时候,才发现自己读的有多粗糙,也体会到只有实践...

2062

扫码关注云+社区

领取腾讯云代金券