通用异常处理框架
异常捕捉、处理是每个项目中必不可少的一部分,利用反射和XML配置技术实现一个通用的、灵活的、可配置的、高度可自扩展的异常处理框架对项目的整体健壮性以及异常处理效率都是非常重要的。通用异常处理框架中需要提供配置信息的支持以及统一的异常处理类和异常日志记录类管理,并允许用户以插件形式扩展自定义的异常处理或日志记录方式。
一、问题的提出
由于异常处理在项目中的普遍性,我们会很自然的想到是否可以对异常处理模块进行提取为公用模块,加强项目间的复用,提高项目的开发进度。
并且在异常处理中,因为没有良好的异常处理系统可能造成一些问题:
l若异常处理方式不当,容易造成比较严重的性能问题。
l在项目开发阶段,开发人员需要得到完整的异常错误信息方便分析BUG;在项目发布阶段,用户希望看到的是比较友好的错误信息。一个良好的异常处理系统应能够通过简单的配置方便的达到这样的效果
l异常处理系统应该能够提供足够的信息方便开发人员对BUG异常的准确定位,减少查找BUG产生原因的时间。(该问题在自己的项目中有过深刻教训:因项目中异常信息的不准确,导致一个BUG消耗大量人力时间)
l一个项目中各层对异常处理的方式不同,可能会不同的层编写不同的代码,造成代码使用麻烦以及复用性低,若需修改,可能改动较大。(例如PDM项目中,除UI层外,都是对异常进行包装后抛给上一层捕捉;而UI层需要处理记录异常,并反馈给用户;即便是UI层也应WebUI和WinUI采用的不同的方式实现,提供给项目的接口也都有一定区别,增加了编码的复杂程度)
l异常报告信息没有统一管理。很多项目中报告给用户的错误消息,都是程序员在开发中自己编写的,对用户而言,常常不是有效的友好的提示信息。集中管理后可以由BA来进行错误信息的整理,改善用户体验。
l异常记录方式可能会在不同环境有不同的处理,应该能通过配置文件简单的实现,而不必每次为不同的应用编写代码。
l要把异常系统做成一个通用的框架,存在一个比较大的挑战:不同的项目可能因项目的整体构架不同,对异常处理方式会有不同的需求,如何保持通用异常处理框架的灵活性的同时实现复用以及可配置性。
二、解决思路
为了最大程度实现异常处理框架的通用性、可扩展性以及可配置性,采用配置文件结合动态加载插件的方式:框架提供接口,由不同项目根据自己的需要实现接口,完成对异常的处理,以及异常日志的记录;而框架再根据配置信息决定异常处理在不同的情况下的处理策略,并通过调用用户实现的接口来完成异常处理过程。
整个异常处理过程对项目调用高度封装,项目中不论任何地方,只需调用一个唯一的接口,框架就会根据配置信息执行需要的异常处理。
三、实践情况
整体结构
框架主要由ExManagement.Config、ExManagement.Interface、ExManagement.MessageHandler以及ExManagement构成。如图:三-1
uExManagement.Config:用于从配置文件中获取配置信息,并将配置信息转换为一个配置信息实体对象,提供给框架的其他部分使用。
uExManagement.Interface:提供异常处理类、异常日志记录类以及异常错误报告类的接口和基类,便于用户在框架的基础上进行自定义的扩展
uExManagement.MessageHandler:对ExManagement.Interface中的IMessage接口实现,提供了WebUI和WinUI下的弹出错误信息提示的支持。也可以直接编写一个满足IMessage接口的包,以自己需要的方式弹出错误提示信息。
uExManagement:是控制和管理框架按照配置信息进行处理的控制类。首先根据异常处理配置加载需要的异常处理类和日志记录类,再按照配置信息内容对异常进行处理。并提供唯一的接口供项目调用。
uExManagement.Handler:默认的异常处理包,包含了一种对异常处理的具体实现。用户也可以继承ExManagement.Interface中的ExHandlerBase基类,实现自定义的异常处理类,并通过修改配置文件让框架调用。
uExManagement.LogHandler:默认日志记录包,包含一种将异常日志记录到数据库的具体实现。用户也可以实现ExManagement.Interface中的ILogHandler接口,实现自定义的异常处理类,并通过修改配置文件让框架调用。
图三-1
ExManagement.Config包
该包主要由两部分组成:实现System.Configuration.IConfigurationSectionHandler接口的ExSectionHandler类和配置信息实体类ExManagerConfig。如图三-2。
ExManagerConfig类中又定义了3个嵌入类:ExHandlerConfig、LogHandlerConfig、LogHandlerConfigCollection以及三个枚举ExAlertType、ErrorCodeSource、ExReturnMode。ExManagerConfig、ExHandlerConfig、LogHandlerConfig分别对应配置文件中的<exmanager></exmanager>、<exhandler></exhandler>、<loghandler></loghandler>节点。
ExManagerConfig是一个集合类,从System.Collections.CollectionBase继承,包含若干个ExHandlerConfig对象,并通过索引器访问包含的ExHandlerConfig对象,支持以int和string两种方式索引。
ExHandlerConfig中除了包含对应节的属性外,还包含一个LogHandlerConfigCollection对象,是LogHandler类的集合类。
图三-2
ExSectionHandler类从配置文件中按照固定的格式获取到相应的配置信息,并将信息填充到ExManagerConfig。
配置文件
<configuration> <configSections> <sectionname="ExManager"type="ExManagement.Config.ExSectionHandler, ExManagement.Config"/> < SPAN>configSections>
<ExManagerErrorCodeSource="XML/DB"ConnectionString="" DataTable=""> <ExHandlerName=""Type=""ReturnMode=""AlertType="WebUI/WinUI/None"/> <ExHandlerName=""Type=""ReturnMode=""AlertType=""> <LogHandlerType=""ConnectionString="" DataTable=""/> <LogHandlerType=""ConnectionString="" DataTable=""/> < SPAN>ExHandler> < SPAN>ExManager> < SPAN>configuration> |
首先需要在Web.Config或者App.Config中添加这行
<sectionname="ExManager"type="ExManagement.Config.ExSectionHandler, ExManagement.Config"/> |
这句是指定用ExManagement.Config.ExSectionHandler类来处理ExManager配置节点
u<exmanager></exmanager>节点
包含ErrorCodeSource和ConnectionString两个属性。
nErrorCodeSource属性。该属性是设置从何种数据源获取错误编号与错误信息的对应关系。其值只能是枚举ErrorCodeSource的值:XML或者DB。
nConnectionString属性。该属性是设置数据源的位置。若ErrorCodeSource属性设置的是XML,则此处为XML文件的FullName;若ErrorCodeSource属性设置的是DB,则此处为数据库的连接字符串。
nDataTable属性。设置记录异常日志使用的表名。
u<exhandler></exhandler>节点
此节点为<exmanager></exmanager>节点的子节点,<exmanager></exmanager>至少得有一个以上的<exhandler></exhandler>子节点。包含Name、Type、ReturnMode以及AlertType属性。
nName属性。为该异常处理器确定一个名称。在项目中调用异常处理方法时,需要指定这个名称。一般建议用层的名字或者项目的名字。
nType属性。指定异常处理器的类,格式为Type=”ClassName,AssemblyName”。该类必须从ExHandlerBase继承。
nReturnMode属性。异常处理器处理后返回给项目的值类型。对应枚举ExReturnMode的值,只能取Exception/ExceptionString/ErrorCode/ErrorString,分别含义是:抛出异常/返回异常详细描述/返回错误编号/返回错误信息。
nAlertType属性。报告异常信息的处理方式。对应枚举ExAlertType的值,只能取None/WebUI/WinUI。
u< LogHandler>节点
此节点为<exhandler></exhandler>节点的子节点,<exhandler></exhandler>节点可以有0到若干个< LogHandler>子节点。
nType属性,用于指定日志记录处理器的类。格式为Type=”ClassName,AssemblyName”。该类必须实现IExLogHandler接口。
nConnectionString属性。该属性是设置记录异常日志的数据源位置。
nDataTable属性。设置记录异常日志使用的表名。
ExManagement.Interface包
包含了IExLogHandler和I Message两个接口以及ExHandlerBase基类,可以通过实现这些接口来对框架进行扩展。如图三-3:
图三-3
以下是ExHandlerBase基类中加载所有异常日志对象的方法:
publicvoidLoadLogHandler(ExManagerConfig.ExHandlerConfigconfig) { m_ExHandlerConfig = config; foreach(ExManagerConfig.LogHandlerConfiglogConfiginconfig.LogConfigColletion) { objectexLogHandler = System.Activator.CreateInstance(Type.GetType(logConfig.Type)); lstLogHandler.Add(exLogHandler); } } |
通过遍历配置文件中该<exhandler></exhandler>节包含的所有<loghandler></loghandler>子节点,并将配置中指定的LogHandler类反射实例化,将对象存入队列中。
ExManagement.MessageHandler包
该包包含两个IMessage接口的实现,分别完成对异常信息在WinUI和WebUI中的弹出提示功能。WinFormMessage和WebMessage分别引用了System.Web.Dll和System. Windows.Forms.Dll。如图三-4。
图三-4
ExManagement包
包含了ExManager类和ErrorInfo类,是框架中核心的业务流程控制模块。如图三-5
图三-5
uErrorInfo类:根据ErrorId查询对应的ErrorString。在这个类里,会根据在配置文件中<exmanager></exmanager>节的值去指定的数据源(XML文件或者数据库)查询。
uExManager类,该类是一个单例类,会在第一次实例化时获取配置信息实体对象,并按照配置文件把所有指定的异常处理类实例化后并存入一个哈希表中。以后实例化该类都会重复使用之前实例化的该对象,避免反射造成的性能影响。通过调用该类的ProcessException()方法将捕捉到的异常对象,错误编号,用来处理异常的异常处理器名(建议为该层的名称)传递给框架,并按照参数从哈希表中取出对应的异常处理对象,调用接口对异常进行处理。
ExManagement.Handler包
只包含一个默认的异常处理器类:DefaultExHandler,它从ExHandlerBase基类继承。下面是该类里最重要的ProcessExeception方法。
publicoverridestringProcessExeception(Exceptionex,stringstrUserId,stringstrErrorCode) { //记录异常信息 LogEx(ex, strUserId, strErrorCode); switch(m_ExHandlerConfig.ReturnMode) { //返回类型为错误编码 caseExManagement.ExReturnMode.ErrorCode: { returnstrErrorCode; } //返回类型为详细错误信息(友好提示) caseExManagement.ExReturnMode.ErrorString: { returnExManagement.ExManager.GetErrorInfoByCode(strErrorCode); } //返回类型为详细异常信息(Exception.Message) caseExManagement.ExReturnMode.ExceptionString: { returnex.Message; } //将异常向上抛出 caseExManagement.ExReturnMode.Exception: { //如果是该层自身引发的异常则包装后抛出 if(ex.GetBaseException() == ex) { thrownewException(string.Format("{0}层发生异常:{1},{2}", m_ExHandlerConfig.Name, strErrorCode, ex.Message), ex); } //若是捕捉到的是包装后的异常(即上层抛出的) else { throwex; } } default: { returnnull; } } } |
在这个默认的异常处理类的ProcessExeception方法中首先调用该类中处理日志记录的方法,再根据配置中的ExReturnMode决定返回处理的结果方式,对异常进行处理。特别是当设置为返回方式为Exception,即抛出异常对象时是先判断该异常是否是最初发生的异常,还是已经处理包装过的异常,避免重复处理异常(不管异常是来自本层或者其他层)。
ExManagement.LogHandler包
该包只有一个默认的DefaultLogHandler类,实现了ILogHandler接口,它负责把异常信息记录到数据库中。
我的项目中使用的各层配置文件(示例)
uBusinessLogicLayer
<ExManagerErrorCodeSource="DB"ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password="DataTable="ErrorInfo"> <ExHandlerName="BusinessLogicLayer"Type="ExManagement.Handler.DefaultExHandler, ExManagement.Handler"ReturnMode="Exception"AlertType="None"/> <LogHandlerType="ExManagement.LogHandler.DefaultLogHandler, ExManagement.LogHandler"ConnectionString="Data Source=PDMDEV151.ITDEV.ZTE.COM.CN;user id=PDM;password="DataTable="ExceptionLog"/> < SPAN>ExHandler> < SPAN>ExManager> |
u配置意义为:根据ErrorId到数据库ErrorInfo库中获取ErrorString,定义了一个ExHandler,名为