如何在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。