通用異常處理框架
異常捕捉、處理是每個項目中必不可少的一部分,利用反射和XML配置技術實現一個通用的、靈活的、可配置的、高度可自擴展的異常處理框架對項目的整體健壯性以及異常處理效率都是非常重要的。通用異常處理框架中需要提供配置信息的支持以及統一的異常處理類和異常日志記錄類管理,并允許用戶以插件形式擴展自定義的異常處理或日志記錄方式。
一、問題的提出
由于異常處理在項目中的普遍性,我們會很自然的想到是否可以對異常處理模塊進行提取為公用模塊,加強項目間的復用,提高項目的開發進度。并且在異常處理中,因為沒有良好的異常處理系統可能造成一些問題:
- 若異常處理方式不當,容易造成比較嚴重的性能問題。
- 在項目開發階段,開發人員需要得到完整的異常錯誤信息方便分析BUG;在項目發布階段,用戶希望看到的是比較友好的錯誤信息。一個良好的異常處理系統應能夠通過簡單的配置方便的達到這樣的效果
- 異常處理系統應該能夠提供足夠的信息方便開發人員對BUG異常的準確定位,減少查找BUG產生原因的時間。(該問題在自己的項目中有過深刻教訓:因項目中異常信息的不準確,導致一個BUG消耗大量人力時間)
- 一個項目中各層對異常處理的方式不同,可能會不同的層編寫不同的代碼,造成代碼使用麻煩以及復用性低,若需修改,可能改動較大。(例如PDM項目中,除UI層外,都是對異常進行包裝后拋給上一層捕捉;而UI層需要處理記錄異常,并反饋給用戶;即便是UI層也應WebUI和WinUI采用的不同的方式實現,提供給項目的接口也都有一定區別,增加了編碼的復雜程度)
- 異常報告信息沒有統一管理。很多項目中報告給用戶的錯誤消息,都是程序員在開發中自己編寫的,對用戶而言,常常不是有效的友好的提示信息。集中管理后可以由BA來進行錯誤信息的整理,改善用戶體驗。
- 異常記錄方式可能會在不同環境有不同的處理,應該能通過配置文件簡單的實現,而不必每次為不同的應用編寫代碼。
- 要把異常系統做成一個通用的框架,存在一個比較大的挑戰:不同的項目可能因項目的整體構架不同,對異常處理方式會有不同的需求,如何保持通用異常處理框架的靈活性的同時實現復用以及可配置性。
二、解決思路
為了最大程度實現異常處理框架的通用性、可擴展性以及可配置性,采用配置文件結合動態加載插件的方式:框架提供接口,由不同項目根據自己的需要實現接口,完成對異常的處理,以及異常日志的記錄;而框架再根據配置信息決定異常處理在不同的情況下的處理策略,并通過調用用戶實現的接口來完成異常處理過程。
整個異常處理過程對項目調用高度封裝,項目中不論任何地方,只需調用一個唯一的接口,框架就會根據配置信息執行需要的異常處理。
三、實踐情況
整體結構
框架主要由ExManagement.Config、ExManagement.Interface、ExManagement.MessageHandler以及ExManagement構成。如圖:三-1
- ExManagement.Config:用于從配置文件中獲取配置信息,并將配置信息轉換為一個配置信息實體對象,提供給框架的其他部分使用。
- ExManagement.Interface:提供異常處理類、異常日志記錄類以及異常錯誤報告類的接口和基類,便于用戶在框架的基礎上進行自定義的擴展
- ExManagement.MessageHandler:對ExManagement.Interface中的IMessage接口實現,提供了WebUI和WinUI下的彈出錯誤信息提示的支持。也可以直接編寫一個滿足IMessage接口的包,以自己需要的方式彈出錯誤提示信息。
- ExManagement:是控制和管理框架按照配置信息進行處理的控制類。首先根據異常處理配置加載需要的異常處理類和日志記錄類,再按照配置信息內容對異常進行處理。并提供唯一的接口供項目調用。
- ExManagement.Handler:默認的異常處理包,包含了一種對異常處理的具體實現。用戶也可以繼承ExManagement.Interface中的ExHandlerBase基類,實現自定義的異常處理類,并通過修改配置文件讓框架調用。
- ExManagement.LogHandler:默認日志記錄包,包含一種將異常日志記錄到數據庫的具體實現。用戶也可以實現ExManagement.Interface中的ILogHandler接口,實現自定義的異常處理類,并通過修改配置文件讓框架調用。
圖三-1
ExManagement.Config包
該包主要由兩部分組成:實現System.Configuration.IConfigurationSectionHandler接口的ExSectionHandler類和配置信息實體類ExManagerConfig。如圖三-2。
ExManagerConfig類中又定義了3個嵌入類:ExHandlerConfig、LogHandlerConfig、LogHandlerConfigCollection以及三個枚舉ExAlertType、ErrorCodeSource、ExReturnMode。ExManagerConfig,ExHandlerConfig、LogHandlerConfig分別對應配置文件中的 、 、 節點。ExManagerConfig是一個集合類,從System.Collections.CollectionBase繼承,包含若干個ExHandlerConfig對象,并通過索引器訪問包含的ExHandlerConfig對象,支持以int和string兩種方式索引。ExHandlerConfig中除了包含對應節的屬性外,還包含一個LogHandlerConfigCollection對象,是LogHandler類的集合類。
圖三-2
ExSectionHandler類從配置文件中按照固定的格式獲取到相應的配置信息,并將信息填充到ExManagerConfig。
配置文件
<configuration> <configSections> <section name="ExManager" type="ExManagement.Config.ExSectionHandler, ExManagement.Config" /> configSections>
<ExManager ErrorCodeSource="XML/DB" ConnectionString="" DataTable=""> <ExHandler Name="" Type="" ReturnMode="" AlertType="WebUI/WinUI/None"/> <ExHandler Name="" Type="" ReturnMode="" AlertType=""> <LogHandler Type="" ConnectionString="" DataTable=""/> <LogHandler Type="" ConnectionString="" DataTable=""/> ExHandler> ExManager> configuration> |
首先需要在Web.Config或者App.Config中添加這行
<section name="ExManager" type="ExManagement.Config.ExSectionHandler, ExManagement.Config" /> |
這句是指定用ExManagement.Config.ExSectionHandler類來處理ExManager配置節點
- 節點 包含ErrorCodeSource和ConnectionString兩個屬性。
- ErrorCodeSource屬性 該屬性是設置從何種數據源獲取錯誤編號與錯誤信息的對應關系。其值只能是枚舉ErrorCodeSource的值:XML或者DB。
- ConnectionString屬性 該屬性是設置數據源的位置。若ErrorCodeSource屬性設置的是XML,則此處為XML文件的FullName;若ErrorCodeSource屬性設置的是DB,則此處為數據庫的連接字符串。
- DataTable屬性 設置記錄異常日志使用的表名。
- 節點 此節點為 節點的子節點,至少得有一個以上的子節點。包含Name、Type、ReturnMode以及AlertType屬性。
- Name屬性 為該異常處理器確定一個名稱。在項目中調用異常處理方法時,需要指定這個名稱。一般建議用層的名字或者項目的名字。
- Type屬性 指定異常處理器的類,格式為Type=”ClassName, AssemblyName”。該類必須從ExHandlerBase繼承。
- ReturnMode屬性 異常處理器處理后返回給項目的值類型。對應枚舉ExReturnMode的值,只能取Exception/ExceptionString/ErrorCode/ErrorString,分別含義是:拋出異常/返回異常詳細描述/返回錯誤編號/返回錯誤信息。
- AlertType屬性 報告異常信息的處理方式。對應枚舉ExAlertType的值,只能取None/WebUI/WinUI。
- < LogHandler>節點 此節點為節點的子節點,節點可以有0到若干個< LogHandler>子節點。
- Type屬性 用于指定日志記錄處理器的類。格式為Type=”ClassName, AssemblyName”。該類必須實現IExLogHandler接口。
- ConnectionString屬性 該屬性是設置記錄異常日志的數據源位置。
- DataTable屬性 設置記錄異常日志使用的表名。
ExManagement.Interface包
包含了IExLogHandler和I Message兩個接口以及ExHandlerBase基類,可以通過實現這些接口來對框架進行擴展。如圖三-3:
圖三-3
以下是ExHandlerBase基類中加載所有異常日志對象的方法:
public void LoadLogHandler(ExManagerConfig.ExHandlerConfig config) { m_ExHandlerConfig = config; foreach (ExManagerConfig.LogHandlerConfig logConfig in config.LogConfigColletion) { object exLogHandler = System.Activator.CreateInstance(Type.GetType(logConfig.Type)); lstLogHandler.Add(exLogHandler); } } |
通過遍歷配置文件中該節包含的所有子節點,并將配置中指定的LogHandler類反射實例化,將對象存入隊列中。
ExManagement.MessageHandler包
該包包含兩個IMessage接口的實現,分別完成對異常信息在WinUI和WebUI中的彈出提示功能。WinFormMessage和WebMessage分別引用了System.Web.Dll和System. Windows.Forms.Dll。如圖三-4。
圖三-4
ExManagement包
包含了ExManager類和ErrorInfo類,是框架中核心的業務流程控制模塊。如圖三-5
圖三-5
ErrorInfo類:根據ErrorId查詢對應的ErrorString。在這個類里,會根據在配置文件中節的值去指定的數據源(XML文件或者數據庫)查詢。
ExManager類,該類是一個單例類,會在第一次實例化時獲取配置信息實體對象,并按照配置文件把所有指定的異常處理類實例化后并存入一個哈希表中。以后實例化該類都會重復使用之前實例化的該對象,避免反射造成的性能影響。通過調用該類的ProcessException()方法將捕捉到的異常對象,錯誤編號,用來處理異常的異常處理器名(建議為該層的名稱)傳遞給框架,并按照參數從哈希表中取出對應的異常處理對象,調用接口對異常進行處理。
ExManagement.Handler包
只包含一個默認的異常處理器類:DefaultExHandler,它從ExHandlerBase基類繼承。下面是該類里最重要的ProcessExeception方法。
public override string ProcessExeception(Exception ex, string strUserId, string strErrorCode) { // 記錄異常信息 LogEx(ex, strUserId, strErrorCode);
switch(m_ExHandlerConfig.ReturnMode) { // 返回類型為錯誤編碼 case ExManagement.ExReturnMode.ErrorCode: { return strErrorCode; } // 返回類型為詳細錯誤信息(友好提示) case ExManagement.ExReturnMode.ErrorString: { return ExManagement.ExManager.GetErrorInfoByCode(strErrorCode); } // 返回類型為詳細異常信息(Exception.Message) case ExManagement.ExReturnMode.ExceptionString: { return ex.Message; } // 將異常向上拋出 case ExManagement.ExReturnMode.Exception: { // 如果是該層自身引發的異常則包裝后拋出 if(ex.GetBaseException() == ex) { throw new Exception(string.Format("{0}層發生異常:{1},{2}", m_ExHandlerConfig.Name, strErrorCode, ex.Message), ex); } // 若是捕捉到的是包裝后的異常(即上層拋出的) else { throw ex; } } default: { return null; } } } |
在這個默認的異常處理類的ProcessExeception方法中首先調用該類中處理日志記錄的方法,再根據配置中的ExReturnMode決定返回處理的結果方式,對異常進行處理。特別是當設置為返回方式為Exception,即拋出異常對象時是先判斷該異常是否是最初發生的異常,還是已經處理包裝過的異常,避免重復處理異常(不管異常是來自本層或者其他層)。
ExManagement.LogHandler包
該包只有一個默認的DefaultLogHandler類,實現了ILogHandler接口,它負責把異常信息記錄到數據庫中。
我的項目中使用的各層配置文件(示例)
BusinessLogicLayer
<ExManager ErrorCodeSource="DB" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataTable="ErrorInfo"> <ExHandler Name="BusinessLogicLayer" Type="ExManagement.Handler.DefaultExHandler, ExManagement.Handler" ReturnMode="Exception" AlertType="None"/> <LogHandler Type="ExManagement.LogHandler.DefaultLogHandler, ExManagement.LogHandler" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataTable ="ExceptionLog"/> ExHandler> ExManager> |
配置意義為:根據ErrorId到數據庫ErrorInfo庫中獲取ErrorString,定義了一個ExHandler,名為BusinessLogicLayer的框架默認的異常處理類,異常返回方式為拋出異常對象,因為不是UI層,所以AlerType為None,該異常處理類用一個默認的LogHandler把異常日志記錄到數據庫ExceptionLog。
BusinessFacadeLayer
<ExManager ErrorCodeSource="DB" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataBase="ErrorInfo"> <ExHandler Name="BusinessFacadeLayer" Type="ExManagement.Handler.DefaultExHandler, ExManagement.Handler" ReturnMode="Exception" AlertType="None"/> <LogHandler Type="ExManagement.LogHandler.DefaultLogHandler, ExManagement.LogHandler" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataTable ="ExceptionLog"/> ExHandler> ExManager> |
該層配置除ExHandler的Name不同外,與BusinessFacadeLayer的配置基本一致。
WebUILayer
<ExManager ErrorCodeSource="DB" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataBase="ErrorInfo"> <ExHandler Name="WebUILayer" Type="ExManagement.Handler.DefaultExHandler, ExManagement.Handler" ReturnMode="ExceptionString" AlertType="WebUI"/> <LogHandler Type="ExManagement.LogHandler.DefaultLogHandler, ExManagement.LogHandler" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataTable ="ExceptionLog"/> ExHandler> ExManager> |
該層的異常處理器的ReturnMode方式為ExceptionString(即Exception.Message),彈出提示方式為WebUI。
WinUILayer
<ExManager ErrorCodeSource="DB" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataBase="ErrorInfo"> <ExHandler Name="WinUILayer" Type="ExManagement.Handler.DefaultExHandler, ExManagement.Handler" ReturnMode="ExceptionString" AlertType="WinUI"/> <LogHandler Type="ExManagement.LogHandler.DefaultLogHandler, ExManagement.LogHandler" ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password=" DataTable ="ExceptionLog"/> ExHandler> ExManager> |
該層和WebUILayer的配置除AlertType為WinUI方式外,基本一致。
以上配置為開發調試方式時,若是在發布到測試或正式環境,只需要把WinUILayer和WebUILayer中的ReturnMode屬性更改為ErrorString方式,即可讓用戶看到的是友好的錯誤信息。
補充:WinUI項目的異常日志記錄器可以再增添一個本地異常Log文件方式,當發生異常時,可以根據用戶提供的Log文件進行分析。
項目中調用
在寫好配置文件之后,項目中引用ExManagement包。調用方法如下,項目中任何地方調用處理方式完全一致
// 返回值根據用戶配置而不同,可以為ErrorId, ErrorString以及ExceptionString string strMessage; try { ; } catch(Exception ex) { // 參數ex, 異常對象 // 參數"BLL001", ErrorId, 即錯誤編號 // 參數"BusinessLogicLayer", ExHandlerName, 即異常處理器名稱, 建議于層名稱對應 // 參數UserId, 即當前用戶Id ProcessExeception(ex, "BLL0001", "BusinessLogicLayer", UserId); } |
四、效果評價
可配置性。通過該異常處理框架可以方便的對異常處理進行需要的配置。可配置的內容:
可以配置多個異常日志記錄以不同的方式記錄在不同的位置;
異常處理方式可以有多種:拋出包裝后異常對象、 返回詳細的異常信息(調試用)、返回錯誤提示信息(發布后給用戶看)以及錯誤編號;
靈活性。建立在可配置性的基礎上,可以組合出多種異常處理方案,以滿足不同項目的特殊需要。
開放的可擴展性。用戶可以自行實現框架提供的接口,自行擴展異常處理以及異常日志記錄的類,以插件形式供框架調用,以實現最大可能的靈活性。
性能。因為使用了不少反射技術,在性能上有一定損耗,但使用了單例模式來彌補,只在項目第一出現異常的時候反射加載對象,以后再次調用時則直接使用該對象,對效率基本沒有任何損耗了。
而多數情況下,以框架提供的默認解決方案已經能夠滿足普通項目的需要,提供一個功能比較完整的,健壯的異常處理機制:
1) 方便和簡化了開發人員及時定位和發現異常原因;
2) 對系統運行狀況提供了強有力的數據支持,并使錯誤信息統一的方式管理,可以改善用戶體驗;
3) 當項目在用戶使用中發現運行錯誤時,可以記下系統反饋的異常記錄編號后于項目開發人員聯系,而開發人員可以根據記錄編號得到異常發生的詳細信息進行分析。有助于縮短項目異常反饋時間。
五、推廣建議
該異常處理框架基本適合所有.Net項目,因為可以靈活的配置以適應不同項目的具體需要。
在一個項目推廣中,只需要有一個人比較深入的了解該異常處理框架的原理以及如何進行配置和自定義擴展開發,掌握時間大概只需要半天到一天時間。而項目中其他人員無須知道該框架的運行機制,他們只需要在每個捕捉異常的地方用同樣的、唯一的方法調用框架即可。
因為該框架對于項目而言是高度聚合,低耦合的,對于項目而言不需要知道異常究竟會被如何處理,減少對項目的依賴。因此對于現有異常處理系統存在不足以及新項目是應該大力推薦使用該框架進行異常處理的,并且對現有項目的改造工作不大;當然也可以選擇對項目已有部分不改動,新開發部分進行使用該框架進行異常處理也是完全可以的。
異常處理框架本身沒有做任何自身的異常情況處理,所以在采用框架的時候需要先按照預想的配置在模擬環境中進行調試,確認能夠正常運行之后再加入到正式項目中去,避免在正式環境中出現框架本身異常無法判斷的情況。
當然因為異常處理的可能方案比較多,該框架的第一個版本可能會有遺漏的可能,但因為框架本身的良好擴展性,多數的特殊情況應該可以用戶自行擴展解決。若有無法解決的可以和我聯系,對框架本身代碼做調整,以求完善該框架。
另外,該異常處理框架若與最近討論比較熱門的AOP(Aspect Oriented Programming面向方面編程)思想結合可以最大程度使系統的業務代碼和系統異常處理代碼完全分離,并提供更為準確的異常信息。因AOP技術目前在發展階段,并需要完全的純OOP項目中實施,暫不對此展開討論。