在实际项目中,日志处理是一项常见的功能,如何统一处理异常?
一般的做法是会写个LogProvider类或ILogProvider接口,然后把错误信息通过这个provider写入文件或者数据库等等。
如:
try
{
helloWorld.Show();
}
catch
(Exception ex)
{
LogProvider.Write(ex.StackTrace);
MessageBox.show(
"
发生错误
"
);
}
这样会到处充斥着LogProvider.Write(ex.StackTrace);
这样类似的代码,是不是闻到了坏味道?
是的,我们需要想办法放在一个统一的地方,一是代码美观,二是也便于维护。
这个时候我们的Spring.net出马了。利用Spring的AOP,,通过代理,我们可以很方便的统一处理这些异常信息。
当然,异常的记录一般是通过日志模块来处理。所以我们先实现一个日志模块,这里用大名鼎鼎的Log4Net,
稍作封装后,代码如下:
代码
1
using
System;
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 异常。
代码
1
using
System;
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;
using
System.Collections.Generic;
using
System.Text;
namespace
LogTest
{
public
interface
IHelloWorld
{
void
Show();
}
}
代码
using
System;
using
System.Collections.Generic;
using
System.Text;
namespace
LogTest
{
public
class
HelloWorld:IHelloWorld
{
#region
IHelloWorld 成员
public
void
Show()
{
throw
new
Exception(
"
发生异常
"
);
}
#endregion
}
}
然后就是配置文件了
objects.xml
代码
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
<
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
代码
<?
xml version
=
"
1.0
"
encoding
=
"
utf-8
"
?>
<
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;
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)