AOP及PIAB简介
AOP(Aspect Oriented Programming),中文称为面向方面编程。AOP是OOP的延续,其意义在于将日志、监控、异常等逻辑与主要的业务逻辑分离开,达到解耦的目的。日志、监控、异常等称为方面(切面),实现AOP后这些逻辑可以独立的变化而不会影响到主要的业务逻辑。
常见的方面主要有:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 延时加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
.net中AOP的实现方法大体分为预编译方式和运行期动态代理方式两种,本文所使用的PIAB使用的是运行期动态代理方式。PIAB是微软企业库(http://entlib.codeplex.com/)的一个模块(企业库从3.0版本开始支持此模块,目前版本为5.0)。
使用PIAB
首先从PIAB的配置了解一下,使用PIAB的几个主要元素。下图是用企业库配置工具打开的一段示例配置。
PIAB内置了9种匹配规则:
Assembly Matching Rule——程序集匹配规则
Member Name Matching Rule——名称匹配规则
Method Signature Matching Rule——方法签名匹配规则
Namespace Matching Rule——命名空间匹配规则
Parameter Type Matching Rule——参数类型匹配规则
Property Matching Rule——属性匹配规则
Return Type Matching Rule——返回类型匹配规则
Tag Attribute Matching Rule——Tag特性匹配规则
Type Matching Rule——类型匹配规则
这些默认的匹配规则只需在配置文件中配置(或者代码中简单添加Attribute),无需编码,可以满足大多数的需求,此外,PIAB还提供了两种可供用户扩展的匹配规则:
Custom Attribute Matching Rule——自定义特性匹配规则
Custom Matching Rule——自定义匹配规则
同样PIAB也内置了5个方面逻辑的处理程序:
Authorization Call Handler——权限逻辑处理程序,与企业库Security模块关联
Exception Handling Call Handler——异常逻辑处理程序,与企业库Exception Handling模块关联
Logging Call Handler——日志逻辑处理程序,与企业库Logging模块关联
Performance Counter Call Handler——性能计数器处理程序
Validation Call Handler——验证逻辑处理程序,与企业库Validation模块关联
除Performance Counter Call Handler外,其它处理程序均与企业库其他模块关联。如果项目中大量使用了企业库的组件,那么通过配置就可以完成相当一部分工作了。当然有时内置的处理程序不能满足需求,或者我们想实现一些其它的操作,所以PIAB同样提供了一个可供自定义的处理程序。
Custom Call Handler——自定义逻辑处理程序
演示示例
下面通过一个简单的示例演示,如何使用PIAB。由于内置的匹配规则和逻辑处理程序仅需要配置即可实现,因此本示例主要展示用户自定义的部分。
1、新建一个控制台应用程序(PolicyInjectionTest),添加PIAB相关的引用。这里采用的企业库版本是5.0.414.0。
Microsoft.Practices.EnterpriseLibrary.Common.dll
Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll
Microsoft.Practices.Unity.dll
Microsoft.Practices.Unity.Interception.dll
2、新建一个接口IAOPTest,用来定义主业务逻辑。
1: using System;
2:
3: namespace PolicyInjectionTest
4: {
5: /// <summary>
6: /// 主业务逻辑接口
7: /// </summary>
8: public interface IAOPTest
9: {
10: void Test(string content);
11: }
12: }
13:
1: using System;
2:
3: namespace PolicyInjectionTest
4: {
5: /// <summary>
6: /// 主业务逻辑实现类
7: /// </summary>
8: public class AOPTest : IAOPTest
9: {
10: [CustomAOP]
11: //[CustomAOPCallHandler("debug")]
12: public void Test(string content)
13: {
14: if (string.IsNullOrEmpty(content))
15: {
16: throw new Exception("参数异常");
17: }
18: Console.WriteLine(content);
19: }
20: }
21: }
22:
23:
参数content如果为空时,则人为引发一个异常。注意主业务逻辑类必须要继承自MarshalByRefObject或一个接口。
4、实现一个Custom Attribute Matching Rule——自定义特性匹配规则。新建一个类CustomAOPAttribute,继承自System.Attribute,并添加[AttributeUsage(AttributeTargets.Method)],表示这个Attribute只能用于方法。
1: using System;
2:
3: namespace PolicyInjectionTest
4: {
5: [AttributeUsage(AttributeTargets.Method)]
6: public class CustomAOPAttribute : Attribute
7: { }
8: }
9:
5、实现一个Custom Matching Rule——自定义匹配规则,新建一个类CustomAOPMatchRule,继承自IMatchingRule。添加[ConfigurationElementType(typeof(CustomMatchingRuleData))],以便自定义规则可以被配置工具识别。
1: using System.Text;
2: using System.Collections.Specialized;
3:
4: using Microsoft.Practices.Unity.InterceptionExtension;
5: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
6: using Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration;
7:
8: namespace PolicyInjectionTest
9: {
10: [ConfigurationElementType(typeof(CustomMatchingRuleData))]
11: public class CustomAOPMatchRule : IMatchingRule
12: {
13: NameValueCollection attributes = null;
14: public CustomAOPMatchRule(NameValueCollection attributes)
15: {
16: this.attributes = attributes;
17: }
18:
19: /// <summary>
20: /// 是否匹配。此处匹配“方法名”及“方法的第一个参数名”
21: /// </summary>
22: /// <param name="member"></param>
23: /// <returns></returns>
24: public bool Matches(System.Reflection.MethodBase member)
25: {
26: bool result = false;
27: var x = member.GetCustomAttributes(true);
28: if (member.GetParameters() != null)
29: {
30: if (this.attributes["Name"] == member.Name || this.attributes["ParameterName"] == member.GetParameters()[0].Name)
31: {
32: result = true;
33: }
34: }
35:
36: return result;
37: }
38: }
39: }
40:
实现自定义匹配规则的要点是实现Matches方法。这里我们假定匹配方法名和方法的第一个参数名。
6、上面实现了两种自定义匹配规则。下面实现自定义的方面逻辑处理程序。
1: using System;
2: using System.Collections.Generic;
3: using System.Collections.Specialized;
4:
5: using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
6: using Microsoft.Practices.Unity.InterceptionExtension;
7:
8: namespace PolicyInjectionTest
9: {
10: /// <summary>
11: /// 自定义的面向切面处理程序
12: /// </summary>
13: [ConfigurationElementType(typeof(CustomCallHandlerData))]
14: public class CustomAOPCallHandler : ICallHandler
15: {
16: private int order = 0;
17:
18: /// <summary>
19: /// 构造函数(即使没有参数,此构造方法也不可省略,否则会导致异常)
20: /// </summary>
21: /// <param name="attributes">配置文件中所配置的参数</param>
22: public CustomAOPCallHandler(NameValueCollection attributes)
23: {
24: //从配置文件中获取参数值
25: this.RunMode = String.IsNullOrEmpty(attributes["RunMode"]) ? "" : attributes["RunMode"];
26: }
27:
28: /// <summary>
29: /// 构造函数,此构造函数是用于Attribute调用
30: /// </summary>
31: /// <param name="message">运行模式</param>
32: public CustomAOPCallHandler(string runMode)
33: {
34: this.RunMode = runMode;
35: }
36:
37: public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
38: {
39: if (this.RunMode == "debug")
40: {
41: Console.WriteLine("主逻辑之前");
42: }
43:
44: var result = getNext()(input, getNext);
45: if (result.Exception == null)
46: {
47: if (this.RunMode == "debug")
48: {
49: Console.WriteLine("主逻辑之后");
50: }
51: }
52: else
53: {
54: if (this.RunMode == "debug")
55: {
56: Console.WriteLine("异常发生时——" + result.Exception);
57: }
58: }
59: return result;
60: }
61:
62: /// <summary>
63: /// 运行模式
64: /// </summary>
65: public string RunMode { get; set; }
66:
67: /// <summary>
68: /// 执行顺序,默认为0
69: /// </summary>
70: public int Order
71: {
72: get
73: {
74: return this.order;
75: }
76: set
77: {
78: this.order = value;
79: }
80: }
81: }
82: }
83:
代码中Invoke方法是最主要的部分,负责调用主业务逻辑方法,同时插入切面逻辑代码。新建一个类CustomAOPCallHandler继承自ICallHandler,并添加[ConfigurationElementType(typeof(CustomCallHandlerData))],以便自定义规则可以被配置工具识别。代码中演示了最常见的三个切面插入点:主逻辑方法执行前,主逻辑方法执行后,主逻辑方法出现异常时。
var result = getNext()(input, getNext); 这句的意思是继续执行下一个逻辑处理程序,如果没有更多的逻辑处理程序,则执行主逻辑代码。
Order属性表示如果有多个逻辑处理程序是,逻辑处理程序的执行顺序。
带有NameValueCollection参数的构造方法用来从配置文件中获取参数,即使没有参数是,此构造方法也不可省略。
示例中使用了一个RunMode的参数。
上面已经完成一个自定义的方面逻辑处理程序的编写,可以用配置文件的方式进行使用。自定义逻辑处理程序还支持
7、自定义逻辑处理程序还支持以在主逻辑方法上添加Attribute的方法使用。新建一个类CustomAOPCallHandlerAttribute继承自System.Attribute,并添加[AttributeUsage(AttributeTargets.Method)],表示这个Attribute只能用于方法。
1: using System;
2:
3: using Microsoft.Practices.Unity;
4: using Microsoft.Practices.Unity.InterceptionExtension;
5:
6: namespace PolicyInjectionTest
7: {
8: [AttributeUsage(AttributeTargets.Method)]
9: public class CustomAOPCallHandlerAttribute : HandlerAttribute
10: {
11: public CustomAOPCallHandlerAttribute(string runMode)
12: {
13: this.RunMode = runMode;
14: }
15:
16: /// <summary>
17: /// 运行模式
18: /// </summary>
19: public string RunMode { get; set; }
20:
21: public override ICallHandler CreateHandler(IUnityContainer container)
22: {
23: //创建具体Call Handler,并调用
24: CustomAOPCallHandler handler = new CustomAOPCallHandler(this.RunMode);
25: return handler;
26: }
27: }
28: }
29:
30:
8、修改main方法,将上述代码串联起来。
1: using System;
2:
3: using Microsoft.Practices.EnterpriseLibrary.PolicyInjection;
4:
5: namespace PolicyInjectionTest
6: {
7: class Program
8: {
9: static void Main(string[] args)
10: {
11: try
12: {
13: IAOPTest aop = PolicyInjection.Create<AOPTest, IAOPTest>();
14: aop.Test("主业务逻辑");
15: aop.Test(null);
16: }
17: catch
18: {
19: //do nothing
20: }
21: Console.WriteLine("按任意键结束。");
22: Console.ReadKey();
23: }
24: }
25: }
26:
注意第13行,主业务逻辑类需要通过PolicyInjection.Create方法来创建(也可以使用Unity的Interception扩展来创建)。
9、添加一个配置文件App.config,使用企业库配置工具修改配置文件。首先使用步骤4中创建的自定义特性匹配规则,修改改好的配置文件如下:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <configSections>
4: <section name="policyInjection" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration.PolicyInjectionSettings, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
5: </configSections>
6: <policyInjection>
7: <policies>
8: <add name="TestPolicy">
9: <matchingRules>
10: <add type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.MatchingRules.CustomAttributeMatchingRule, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
11: attributeType="PolicyInjectionTest.CustomAOPAttribute, PolicyInjectionTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
12: name="Custom Attribute Matching Rule" />
13: </matchingRules>
14: <handlers>
15: <add RunMode="debug" type="PolicyInjectionTest.CustomAOPCallHandler, PolicyInjectionTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
16: name="CustomAOPCallHandler" />
17: </handlers>
18: </add>
19: </policies>
20: </policyInjection>
21: </configuration>
22:
10、运行查看结果。
打开主逻辑实现类AOPTest类的第11行的注释,可以看到直接使用Attribute标记实现的效果。
11、使用另一种配置,使用步骤5中创建的自定义匹配规则。修改后的配置文件如下:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <configSections>
4: <section name="policyInjection" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration.PolicyInjectionSettings, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" />
5: </configSections>
6: <policyInjection>
7: <policies>
8: <add name="TestPolicy">
9: <matchingRules>
10: <add Name="Test" ParameterName="content" type="PolicyInjectionTest.CustomAOPMatchRule, PolicyInjectionTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
11: name="CustomAOPMatchRule" />
12: </matchingRules>
13: <handlers>
14: <add RunMode="debug" type="PolicyInjectionTest.CustomAOPCallHandler, PolicyInjectionTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
15: name="CustomAOPCallHandler" />
16: </handlers>
17: </add>
18: </policies>
19: </policyInjection>
20: </configuration>
21:
12、运行程序可以得到与步骤10相同的结果。(将AOPTest类的第10、11行注释掉)
参考资料
http://www.cnblogs.com/kyo-yo/archive/2010/09/01/Learning-EntLib-Ninth-Use-PolicyInjection-Module-AOP-PART1-Basic-Use-Of-Dscription.html—— 使用PolicyInjection模块进行AOP
http://www.cnblogs.com/artech/archive/2010/09/15/1826885.html —— 这是EnterLib PIAB的BUG吗