閱讀122 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Enterprise Library深入解析與靈活應用(4):創建一個自定義Exception Handler改變ELAB的異常處理機製

、背景與動機

微軟Enterprise Library ELABException Handling Application Block)提供了一種基於策略(Policy)的異常處理方式,在不同的環境中,比如多層架構中不同的層次中,我們可以定義不同的異常處理策略。對於ELAB來說,Exception Handling Policy = Exception Type + Exception Handler(s) ,也就是說異常處理策略規定了對於某種類型的類型需要通過那些Exception Handler去處理。

從這種意義上講,ELAB的異常處理機製是基於Exception Type的,異常的類型是我們處理異常的最小粒度。對於一個確定的異常處理策略,在不同場合拋出的同種類型的異常,都會使用相同的Exception Handler去處理。舉個例子,對於一個基於SQL ServerData Access操作,對於數據庫連接失敗和違反數據一致性約束,都會拋出SqlException。這兩種情況對於ELAB都是等效的,因為它隻能根據異常的類型進行異常處理。

在很多情況下,這種基於異常類型級別的異常處理並不能解決我們實際異常處理的問題。我們往往需要粒度更細的異常處理機製——對於拋出的同一種類型的異常,根據異常對象具體的屬性值進行相應的異常處理。

、從基於類型的異常處理到基於屬性值異常處理

我們需要實現的目標很清楚,就是對於拋出的異常,我們可以根據某個屬性的具體的值,為其指定對應的Exception Handler進行處理。由於ELAB基於異常類型的Exception Handler的分發機製,我們不能改變,我們隻能采用一些變通的機製實現曲線救國,達到我們基於屬性的分發Exception Handler的目的。

具體的實現方案就是創建一個特殊的Exception Handler,該Exception Handler根據異常對象某個屬性的值,指定相應的Exception Handler。對於這個特殊的Exception Handler來說,他實現了基於屬性值的篩選功能,我們把它命名為FilterableExceptionHandler

一般情況下,異常的類型和對應的Exception Handler通過下圖的形式直接進行匹配。當FooException拋出,兩個Exception HandlerExceptionHandlerAExceptionHandlerB先後被執行。

 

當引入了FilterableExceptionHandler以後,整個結構變成下麵一種形式:FilterableExceptionHandler被指定到FooException,當FooException被拋出的時候,FilterableExceptionHandler被執行。而FilterableExceptionHandler本身並不執行異常處理相關的邏輯,它的工作是根據exception的某個屬性值,創建相對應的ExceptionHandler(s),並使用他們來處理該異常。如下圖所示,當exception.Property=Value1是,創建ExceptionHandlerAExceptionHandlerB處理異常;當exception.Property=Value2時,真正創建出來進行異常處理的是ExceptionHandlerCExceptionHandlerD

 

三、FilterableExceptionHandler的配置

接下來,我們就來創建這樣一個特殊的FilterableExceptionHandler。和一般的自定義Exception Handler一樣,除了定義FilterableExceptionHandler本身之外,還需要定義兩個輔助的類:ExceptionHandlerDataExceptionHandlerAssembler,前者定義ExceptionHandler相關的配置信息;後者通過配置創建相應的ExceptionHandler

我們先來定於FilterableExceptionHandlerExceptionHandlerDataFilterableExceptionHandlerData。在這之前,我們來看看一個FilterableExceptionHandler配置的實例:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <configuration>
   3:  <configSections>
   4:     <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
   5:  </configSections>
   6:  <exceptionHandling>
   7:         <exceptionPolicies>
   8:             <add name="Exception Policy">
   9:                 <exceptionTypes>
  10:                     <add type="Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo"
  11:                         postHandlingAction="ThrowNewException" name="Exception">
  12:                         <exceptionHandlers>
  13:                             <add type="Artech.ExceptionHandlers.FilterableExceptionHandler,Artech.ExceptionHandlers" name="Custom Handler">
  14:                                 <filters>
  15:                                     <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  16:                                         <exceptionHandlers>
  17:                                             <add exceptionMessage="Bar" exceptionMessageResourceType="" 
  18:                                 replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
  19:                                 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  20:                                 name="Replace Handler" />
  21:                                         </exceptionHandlers>
  22:                                     </add>
  23:                                     <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  24:                                         <exceptionHandlers>
  25:                                             <add exceptionMessage="Baz" exceptionMessageResourceType=""
  26:                                 replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
  27:                                 type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  28:                                 name="Replace Handler" />
  29:                                         </exceptionHandlers>
  30:                                     </add>
  31:                                 </filters>
  32:                             </add>
  33:                         </exceptionHandlers>
  34:                     </add>
  35:                 </exceptionTypes>
  36:             </add>
  37:         </exceptionPolicies>
  38:     </exceptionHandling>
  39: </configuration>
  40:  

其中和FilterableExceptionHandler相關的配置集中在如下一段。整個配置的結果是這樣的:<filters>中一個filter列表,定義了對異常對象屬性名/屬性值的篩選和符合該條件的Exception Handler列表。下麵一段配置表達的場景是:對於拋出的異常(Artech.CustomExceptionHandlers.FooException,Artech.CustomExceptionHandlers.Demo),我們需要調用ReplaceHandler用一個另一個異常對其進行替換。具體的替換規則是:如何Message屬性為“xxx”,則將其替換成BarException;如何Message屬性為“yyy”,則替換成BazException。最終的Message分別為“Bar”“Baz”

   1: <filters>
   2:     <add property="Message" value="xxx" name="filter1" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
   3:         <exceptionHandlers>
   4:             <add exceptionMessage="Bar" exceptionMessageResourceType=""
   5: replaceExceptionType="Artech.CustomExceptionHandlers.BarException,Artech.CustomExceptionHandlers.Demo"
   6: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
   7: name="Replace Handler" />
   8:         </exceptionHandlers>
   9:     </add>
  10:     <add property="Message" value="yyy" name="filter2" typeConverter="System.ComponentModel.TypeConverter,System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  11:         <exceptionHandlers>
  12:             <add exceptionMessage="Baz" exceptionMessageResourceType=""
  13: replaceExceptionType="Artech.CustomExceptionHandlers.BazException,Artech.CustomExceptionHandlers.Demo"
  14: type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.ReplaceHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
  15: name="Replace Handler" />
  16:         </exceptionHandlers>
  17:     </add>
  18: </filters>

四、如何創建如何創建FilterableExceptionHandler

對配置有一個初步了解後,我們來定義FilterableExceptionHandlerData

   1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
   2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   3: using System.Configuration;
   4: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder; 
   5: namespace Artech.ExceptionHandlers
   6: {
   7:     [Assembler(typeof(FilterableExceptionHandlerAssembler))]
   8:     public class FilterableExceptionHandlerData:ExceptionHandlerData
   9:     {
  10:         [ConfigurationProperty("filters", IsRequired = true)]
  11:         public NamedElementCollection<FilterEntry> Filters
  12:         {
  13:             get
  14:             {
  15:                 return base["filters"] as NamedElementCollection<FilterEntry>; 
  16:             }
  17:             set
  18:             {
  19:                 this["filters"] = value;
  20:             }
  21:         }
  22:     }
  23: }

FilterableExceptionHandlerData僅僅是FilterEntry的集合。我們接著來看看FilterEntry的定義:

   1: using System;
   2: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   3: using System.Configuration;
   4: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
   5: using System.ComponentModel;
   6: namespace Artech.ExceptionHandlers
   7: {
   8:     public class FilterEntry : NamedConfigurationElement
   9:     {
  10:         [ConfigurationProperty("property", IsRequired = true)]
  11:         public string PropertyName
  12:         {
  13:             get
  14:             {
  15:                 return this["property"] as string;
  16:             }
  17:            set
  18:             {
  19:                 this["property"] = value;
  20:             }
  21:         }
  22:  
  23:         [ConfigurationProperty("value", IsRequired = true)]
  24:         public string PropertyValue
  25:         {
  26:            get
  27:             {
  28:                return this["value"] as string;
  29:             }
  30:             set
  31:             {
  32:                 this["value"] = value;
  33:             }
  34:         }
  35:         [ConfigurationProperty("typeConverter", IsRequired = false, DefaultValue = "")]
  36:         public string TypeConverterData
  37:         {
  38:             get
  39:             {
  40:                 return this["typeConverter"] as string;
  41:             }
  42:            set
  43:             {
  44:                 this["typeConverter"] = value;
  45:             }
  46:         }
  47:         public TypeConverter TypeConverter
  48:         {
  49:             get
  50:             {
  51:                 if (string.IsNullOrEmpty(this.TypeConverterData))
  52:                 {
  53:                     return new TypeConverter();
  54:                 }
  55:  
  56:                 Type typeConverterType = null;
  57:                 try
  58:                 {
  59:                     typeConverterType = Type.GetType(this.TypeConverterData);
  60:                 }
  61:                 catch (Exception ex)
  62:                 {
  63:                     throw new ConfigurationErrorsException(ex.Message);
  64:                 }
  65:                 TypeConverter typeConverter = Activator.CreateInstance(typeConverterType) as TypeConverter;
  66:                 if (typeConverter == null)
  67:                 {
  68:                     throw new ConfigurationErrorsException(string.Format("The {0} is not a valid TypeConverter.", this.TypeConverterData));
  69:                 }
  70:  
  71:                 return typeConverter;
  72:             }
  73:         }
  74:  
  75:         [ConfigurationProperty("exceptionHandlers")]
  76:         public NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData> ExceptionHandlers
  77:         {
  78:             get
  79:             {
  80:                 return (NameTypeConfigurationElementCollection<ExceptionHandlerData, CustomHandlerData>)this["exceptionHandlers"];
  81:             }
  82:         }
  83:     }
  84: }

由於我們需要根據exception的某個屬性來動態指定具體的ExceptionHandler,我們定了3個必要的屬性:PropertyNamePropertyValueExceptionHandlers。他們分別表示用於篩選的屬性名稱和屬性,以及滿足篩選條件所采用的Exception Handler的配置。此外還具有一個額外的屬性:TypeConverter,用於類型的轉化。在進行篩選比較的時候,我們通過反射得到exception某個屬性(PropertyName)的值,然後和指定的值(PropertyValue)進行比較。簡單起見,我們在這裏進行字符串的比較,所以我們需要通過TypeConverter將通過反射得到的屬性值轉換成字符串。默認的TypeConverterSystem.ComponentModel.TypeConverter

接下來我們看看真正的FilterableExceptionHandler的定義:

using System;

using System.Collections.Generic;

using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

using System.Reflection;

using System.Configuration;

using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;

 

namespace Artech.ExceptionHandlers

{

    [ConfigurationElementType(typeof(FilterableExceptionHandlerData))]

    public class FilterableExceptionHandler : IExceptionHandler

    {

        private FilterableExceptionHandlerData _filterableExceptionHandlerData;

 

        public FilterableExceptionHandler(FilterableExceptionHandlerData handlerData)

        {

            this._filterableExceptionHandlerData = handlerData;

        }

 

        private IList<IExceptionHandler> GetFilteredHandler(Exception exception, FilterableExceptionHandlerData handlerData)

        {

            IList<IExceptionHandler> handlers = new List<IExceptionHandler>();

            foreach (FilterEntry filterEntry in handlerData.Filters)

            {

                PropertyInfo propertyInfo = exception.GetType().GetProperty(filterEntry.PropertyName);

                if (propertyInfo == null)

                {

                    throw new ConfigurationErrorsException(

                        string.Format("The {0} does not have the {1} property.", exception.GetType().Name, filterEntry.PropertyName));

                }

                object propertyValue = propertyInfo.GetValue(exception, null);

                if (string.Compare(filterEntry.TypeConverter.ConvertToString(propertyValue), filterEntry.PropertyValue, true) == 0)

                {

                    foreach(ExceptionHandlerData exceptionHandlerData in filterEntry.ExceptionHandlers)

                    {

                        handlers.Add( ExceptionHandlerCustomFactory.Instance.Create(null,exceptionHandlerData,null,null));

                    }

                }

            }

 

            return handlers;

        }

 

        #region IExceptionHandler Members

 

        public Exception HandleException(Exception exception, Guid handlingInstanceId)

        {

            foreach (IExceptionHandler handler in this.GetFilteredHandler(exception, this._filterableExceptionHandlerData))

            {

                exception = handler.HandleException(exception,handlingInstanceId);

            }

 

            return exception;

        }

 

        #endregion

    }

}

FilterableExceptionHandler的構造函數接受一個FilterableExceptionHandlerData 參數。在GetFilteredHandler方法中,我們通過具體的Exception對象和FilterableExceptionHandlerData篩選出真正的ExceptionHandler。邏輯並不複雜:便利FilterableExceptionHandlerData 中的所有FilterEntry,通過反射得到FilterEntry指定的屬性名稱(PropertyName)對應的屬性值;通過TypeConverter轉化成字符串後和FilterEntry指定的屬性值(PropertyValue)進行比較。如果兩者相互匹配,得到FilterEntry所有ExceptionHandlerExceptionHandlerData,通過ExceptionHandlerCustomFactory創建對應的Exception Handler對象。最後將創建的Exception Handler對象加入目標列表。

HandleException方法中,隻需要逐個執行通過GetFilteredHandler方法篩選出來的Exception Handler就可以了。

最後簡單看看FilterableExceptionHandlerAssembler 的定義。

 

   1: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration;
   2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   3: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ObjectBuilder;
   4: using Microsoft.Practices.ObjectBuilder2;
   5: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
   6:  
   7: namespace Artech.ExceptionHandlers
   8: {
   9:     public class FilterableExceptionHandlerAssembler : IAssembler<IExceptionHandler, ExceptionHandlerData>
  10:     {
  11:         #region IAssembler<IExceptionHandler,ExceptionHandlerData> Members
  12:  
  13:         public IExceptionHandler Assemble(IBuilderContext context, ExceptionHandlerData objectConfiguration, 
  14:             IConfigurationSource configurationSource, ConfigurationReflectionCache reflectionCache)
  15:         {
  16:             return new FilterableExceptionHandler(objectConfiguration as FilterableExceptionHandlerData);
  17:         }
  18:  
  19:         #endregion
  20:     }
  21: }
  22:  

現在我們通過一個簡單的Console Application來驗證FilterableExceptionHandler是否能夠按照我們希望的方式進行工作。我們使用在第三節列出的配置。為次我們我需要創建3ExceptionFooException BarException BazException

   1: using System;
   2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   3: using System.Runtime.Serialization; 
   4: namespace Artech.CustomExceptionHandlers
   5: {
   6:     [global::System.Serializable]
   7:     public class FooException : Exception
   8:     {
   9:         public FooException() { }
  10:         public FooException(string message) : base(message) { }
  11:         public FooException(string message, Exception inner) : base(message, inner) { }
  12:         protected FooException(SerializationInfo info,StreamingContext context)
  13:             : base(info, context) { }
  14:     } 
  15:     [global::System.Serializable]
  16:     public class BarException : Exception
  17:     {
  18:         public BarException() { }
  19:         public BarException(string message) : base(message) { }
  20:         public BarException(string message, Exception inner) : base(message, inner) { }
  21:         protected BarException(SerializationInfo info,StreamingContext context)
  22:             : base(info, context) { }
  23:     } 
  24:     [global::System.Serializable]
  25:     public class BazException : Exception
  26:     {
  27:         public BazException() { }
  28:         public BazException(string message) : base(message) { }
  29:         public BazException(string message, Exception inner) : base(message, inner) { }
  30:         protected BazException(SerializationInfo info,StreamingContext context)
  31:             : base(info, context) { }
  32:     }
  33: }  

在通過配置我們可以看到,我們希望的是對FooException 進行異常的處理,並通過Message的屬性,通過ReplaceHandler將其替換成BarException BazException;為此我們寫一個HandleException方法。如下所以,我們人為地拋出一個FooExceptionMessage通過參數指定。在try/catch中,通過ExceptionPolicy.HandleException方法通過 ELAB進行異常的處理。在最外層的catch中,輸出最終的Exception的類型和Message。在Main方法中,兩次調用HandleException方法,在參數中指定FooExceptionMessage“xxx”“yyy”)。

 

   1: using System;
   2: using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
   3: using System.Runtime.Serialization; 
   4: namespace Artech.CustomExceptionHandlers
   5: {
   6:     class Program
   7:     {
   8:         static void Main(string[] args)
   9:        {
  10:             HandleException("xxx");
  11:             HandleException("yyy");            
  12:         } 
  13:         private static void HandleException(string message)
  14:         {
  15:             try
  16:             {
  17:                 try
  18:                 {
  19:                     throw new FooException(message);
  20:                 }
  21:                 catch (Exception ex)
  22:                 {                 
  23:                     if (ExceptionPolicy.HandleException(ex, "Exception Policy"))
  24:                     {
  25:                         throw;
  26:                     } 
  27:                 }
  28:             }
  29:             catch (Exception ex)
  30:             {
  31:                 Console.WriteLine("Exception Type: {0}; Message: {1}", ex.GetType().Name, ex.Message);
  32:             }
  33:         }
  34:     } 
  35: }

運行該程序,你將會看到如下的輸出結果。可見對應拋出的同一種類型的ExceptionFooException),通過我們的FilterableExceptionHandler,根據Message屬性值的不同,最終被分別替換成了BarException BazException 



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

最後更新:2017-10-30 16:34:16

  上一篇:go  WCF中的Binding模型之一: Binding模型簡介
  下一篇:go  [MethodImpl(MethodImplOptions.Synchronized)]、lock(this)與lock(typeof(...))