Spring.Net学习系列一: 统一异常处理
在实际项目中,日志处理是一项常见的功能,如何统一处理异常?
一般的做法是会写个LogProvider类或ILogProvider接口,然后把错误信息通过这个provider写入文件或者数据库等等。
如:
{
helloWorld.Show();
}
catch(Exception ex)
{
LogProvider.Write(ex.StackTrace);
MessageBox.show("发生错误");
}
这样会到处充斥着LogProvider.Write(ex.StackTrace);
这样类似的代码,是不是闻到了坏味道?
是的,我们需要想办法放在一个统一的地方,一是代码美观,二是也便于维护。
这个时候我们的Spring.net出马了。利用Spring的AOP,,通过代理,我们可以很方便的统一处理这些异常信息。
当然,异常的记录一般是通过日志模块来处理。所以我们先实现一个日志模块,这里用大名鼎鼎的Log4Net,
稍作封装后,代码如下:
2 using System.Collections.Generic;
3 using System.Text;
4
5 namespace Log
6 {
7 public class Log
8 {
9 private static Log instance;
10 private static readonly log4net.ILog log = log4net.LogManager.GetLogger("AppLog");
11 private static object obj=new object ();
12 private Log()
13 {
14
15 }
16 /// <summary>
17 /// 获取Log单例
18 /// </summary>
19 /// <returns></returns>
20 public static Log GetInstance()
21 {
22 if (instance == null)
23 {
24 lock (obj)
25 {
26 instance = new Log();
27 log4net.Config.XmlConfigurator.Configure();
28 }
29 }
30 return instance;
31 }
32
33 public void Debug(object o)
34 {
35 if (log.IsDebugEnabled)
36 {
37 if (o is Exception) log.Debug("Debug", o as Exception);
38 else log.Debug(o.ToString());
39
40 }
41 }
42
43 public void Error(object o)
44 {
45 if (log.IsErrorEnabled)
46 {
47 if (o is Exception) log.Error("Error", o as Exception);
48 else log.Error(o.ToString());
49 }
50 }
51
52 public void Warn(object o)
53 {
54 if (log.IsWarnEnabled)
55 {
56 if (o is Exception) log.Warn("Warn", o as Exception);
57 else log.Warn(o.ToString());
58 }
59 }
60
61 public void Info(object o)
62 {
63 if (log.IsInfoEnabled)
64 {
65 if (o is Exception) log.Info("Info", o as Exception);
66 else log.Info(o.ToString());
67 }
68 }
69
70 public void Fatal(object o)
71 {
72 if (log.IsFatalEnabled)
73 {
74 if (o is Exception) log.Fatal("Fatal", o as Exception);
75 else log.Fatal(o.ToString());
76 }
77 }
78 }
79 }
80
接下来我们通过 Spring.Net 的 AOP 特性实现异常的统一处理,如果我们需要在异常发生时做一些操作的话我们就必须实现 Spring.Aop.IThrowsAdvice,该接口没有任何实现方法,是一个空接口,它仅仅做为一个标记接口而存在,但实现了 IThrowsAdvice 接口的类必须定义至少一个 AfterThrowing 方法,方法的签名如下:
AfterThrowing([MethodInfo method, Object[] args, Object target], Exception subclass);
其中中括号括起来的前三个参数是可选的,返回值可以是任意数据类型。Spring.Aop.Framework.Adapter.ThrowsAdviceInterceptor 类实现对实现了 Spring.Aop.IThrowsAdvice 派生类中的方法依赖注入,其中的 ThrowsAdviceInterceptor() 方法检查 Spring.Aop.IThrowsAdvice 的派生类是否定义了至少一个异常处理方法,如果没有则抛出 ArgumentException 异常,MapAllExceptionHandlingMethods() 方法则在定义好的重载方法中查找出异常类型与最后一个参数所定义的类型中最接近的方法,而且我们不应该在其中实现了两个相同异常类型的方法,即使他们的参数数目不同,否则也将抛出 ArgumentException 异常。
2 using System.Collections.Generic;
3 using System.Text;
4 using Spring.Aop;
5 using System.Reflection;
6 namespace Log
7 {
8 public class ExceptionThrowAdvice : IThrowsAdvice
9 {
10 private Log logInstance;
11
12 public ExceptionThrowAdvice()
13 {
14 logInstance = Log.GetInstance();
15 }
16
17 public void AfterThrowing(MethodInfo method, Object[] args, Object target, Exception exception)
18 {
19 logInstance.Error(exception);
20 }
21 }
22 }
23
接下来我们开始测试效果
首先定义一个接口以及实现类
如果使用spring.net+Nhibernate,这里就是Service和IService了(呵呵比较熟悉了吧)
using System.Collections.Generic;
using System.Text;
namespace LogTest
{
public interface IHelloWorld
{
void Show();
}
}
using System.Collections.Generic;
using System.Text;
namespace LogTest
{
public class HelloWorld:IHelloWorld
{
#region IHelloWorld 成员
public void Show()
{
throw new Exception("发生异常");
}
#endregion
}
}
然后就是配置文件了
objects.xml
<objects xmlns="http://www.springframework.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.net
http://www.springframework.net/xsd/spring-objects.xsd">
<object id="ExceptionThrowAdvice" type="Log.ExceptionThrowAdvice, Log" />
<object id="HelloWorld" type="LogTest.HelloWorld, LogTest" />
<object id="HelloWorldProxy" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop" >
<property name="ProxyInterfaces">
<list>
<value>LogTest.IHelloWorld,LogTest</value>
</list>
</property>
<property name="Target">
<ref object="HelloWorld" />
</property>
<property name="InterceptorNames">
<list>
<value>ExceptionThrowAdvice</value>
</list>
</property>
</object>
</objects>
app.config
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core"/>
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core"/>
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<!--spring配置-->
<spring>
<context>
<resource uri="~/objects.xml"/>
</context>
</spring>
<!-- Log输出定义 -->
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.FileAppender" >
<param name="File" value="log.text" />
<param name="datePattern" value="MM-dd HH:mm" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
</layout>
</appender>
<appender name="AppLogAppender" type="log4net.Appender.FileAppender" >
<param name="File" value="AppLog.text" />
<param name="datePattern" value="MM-dd HH:mm" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
</layout>
</appender>
<logger name="Spring">
<level value="INFO" />
<appender-ref ref="LogFileAppender" />
</logger>
<logger name="NHibernate">
<level value="INFO" />
<appender-ref ref="LogFileAppender" />
</logger>
<logger name="AppLog">
<level value="ALL" />
<appender-ref ref="AppLogAppender" />
</logger>
</log4net>
</configuration>
然后在我们的启动程序中
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Log;
namespace LogTest
{
public partial class Form1 : Form
{
private IApplicationContext _ctx;
private IHelloWorld _helloWorld;
public Form1()
{
InitializeComponent();
try
{
helloWorld.Show();
}
catch
{
MessageBox.Show("发生异常");
}
}
private IApplicationContext ctx
{
get
{
if (_ctx == null)
{
_ctx = ContextRegistry.GetContext();
}
return _ctx;
}
}
private IHelloWorld helloWorld
{
get
{
if (_helloWorld == null)
{
_helloWorld = ctx["HelloWorldProxy"] as IHelloWorld;
}
return _helloWorld;
}
}
}
}
。ok大功告成了,看到了吧,代码不记录异常信息时的代码一样,但实现了异常的统计记录,你可以看到在日志文件中已经写入了错误信息。
示例源码点击下载
[下面引用自《Spring 技术手册》]
注意到当异常发生时, Throw Advice 的任务只是执行对应的方法,您并不能在 Throw Advice 中将异常处理掉,在 Throw Advice 执行完毕后,原告的异常仍将传播至应用程序之中, Throw Advice 并不介入应用程序的异常处理,异常处理仍旧是应用程序本身所要负责的,如果想要在 Throw Advice 处理时中止应用程序的处理流程,作法是抛出其它的异常。
运行效果文字描述如下:
程序弹出对话框 “发生异常”
在AppLog.text文件中记录错误信息:
2009-12-13 16:34:17,812 [11] ERROR AppLog [(null)] - Error
System.Exception: 发生异常
在 LogTest.HelloWorld.Show() 位置 F:\Study\LogAop\LogTest\HelloWorld.cs:行号 13
在 _dynamic_LogTest.HelloWorld.Show(Object , Object[] )
在 Spring.Reflection.Dynamic.SafeMethod.Invoke(Object target, Object[] arguments)
在 Spring.Aop.Framework.DynamicMethodInvocation.InvokeJoinpoint()
在 Spring.Aop.Framework.AbstractMethodInvocation.Proceed()
在 Spring.Aop.Framework.Adapter.ThrowsAdviceInterceptor.Invoke(IMethodInvocation invocation)