閱讀582 返回首頁    go 技術社區[雲棲]


如何在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連接斷開、認證失敗、數據庫對象不存在、違反一致性約束等等,如果異常處理框架能夠根據最終拋出的異常的具體屬性,“智能”地應用相應的策略去處理,這才是我們樂於看到的。

為了解決這個問題,我創建了一個特殊的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。


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 15:33:52

  上一篇:go  [WCF 4.0新特性] 路由服務[實例篇]
  下一篇:go  手機APP係統開發流程