基于Enterprise Library PIAB的AOP框架已经在公司项目开发中得到广泛的使用,但是最近同事维护一个老的项目,使用到了Enterprise Library 2,所以PIAB是在Enterprise Library 3.0中推出的,所以不同直接使用。为了解决这个问题,我写了一个通过方法劫持(Method Interception)的原理,写了一个简易版的AOP框架。(如果对PIAB不是很了解的读者,可以参阅我的文章MS Enterprise Library Policy Injection Application Block 深入解析[总结篇])。 Souce Code下载:http://files.cnblogs.com/artech/Artech.SimpleAopFramework.zip
一、如何使用?
编程方式和PIAB基本上是一样的,根据具体的需求创建相应的CallHandler,通过Custom Attribute的形式将CallHandler应用到类型或者方法上面。下面就是一个简单例子。
Code
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using Artech.SimpleAopFramework.Demos;
using System.Configuration;
namespace Artech.SimpleAopFramework
{
class Program
{
static void Main(string[] args)
{
string userID = Guid.NewGuid().ToString();
InstanceBuilder.Create<UserManager, IUserManager>().CreateDuplicateUsers(userID, Guid.NewGuid().ToString());
Console.WriteLine("Is the user whose ID is /"{0}/" has been successfully created! {1}", userID, UserUtility.UserExists(userID) ? "Yes" : "No");
}
}
public class UserManager : IUserManager
{
[ExceptionCallHandler(Ordinal = 1, MessageTemplate = "Encounter error:/nMessage:{Message}")]
[TransactionScopeCallHandler(Ordinal = 2)]
public void CreateDuplicateUsers(string userID, string userName)
{
UserUtility.CreateUser(userID, userName);
UserUtility.CreateUser(userID, userName);
}
}
public interface IUserManager
{
void CreateDuplicateUsers(string userID, string userName);
}
}
在上面例子中,我创建了两个CallHandler:TransactionScopeCallHandler和ExceptionCallHandler,用于进行事务和异常的处理。也就是说,我们不需要手工地进行事务的Open、Commit和Rollback的操作,也不需要通过try/catch block进行手工的异常处理。为了验证正确性,我模拟了这样的场景:数据库中有一个用户表(Users)用于存储用户帐户,每个帐户具有唯一ID,现在我通过UserManager的CreateDuplicateUsers方法插入两个具有相同ID的记录,毫无疑问,如果没有事务的处理,第一次用户添加将会成功,第二次将会失败。反之如果我们添加的TransactionScopeCallHandler能够起作用,两次操作将在同一个事务中进行,重复的记录添加将会导致怎过事务的回退。
在ExceptionCallHandler中,会对抛出的SqlException进行处理,在这我们仅仅是打印出异常相关的信息。至于具有要输出那些信息,可以通过ExceptionCallHandlerAttribute的MessageTemplate 属性定义一个输出的模板。
运行程序,我们会得到这样的结果,充分证明了事务的存在,错误信息也按照我们希望的模板进行输出。
二、设计概要
同PIAB的实现原理一样,我通过自定义RealProxy实现对CallHandler的执性,从而达到方法调用劫持的目的(底层具体的实现,可以参阅我的文章Policy Injection Application Block 设计和实现原理)。下面的UML列出了整个框架设计的所有类型。
- ICallHandler:所有CallHandler必须实现的接口。
- CallHandlerBase:实现了ICallHandler的一个抽象类,是自定义CallHandler的基类。
- HandlerAttribute:所有的CallHandler通过相应的HandlerAttribute被应用到所需的目标对象上。HandlerAttribute是一个继承自Attribute的抽象类,是自定义HandlerAttribute的基类。
- CallHandlerPipeline:由于同一个目标方法上面可以同时应用多个CallHandler,在运行时,他们被串成一个有序的管道,依次执行。
- InterceptingRealProxy<T>:继承自RealProxy,CallHandlerPipeline最终在Invoke方法中执行,从而实现了“方法调用劫持”。
- InvocationContext:表示当前方法执行的上下文,Request和Reply成员表示方法的调用和返回消息。
- InstanceBuidler:由于我们需根据InterceptingRealProxy<T>对象创建TransparentProxy,并通过TransparentProxy进行方法的调用,CallHandler才能在RealProxy中被执行。InstanceBuilder用于方便的创建TransparentProxy对象。
三、具体实现
现在我们来详细分析实现的细节。下来看看表示方法调用上下文的InvocationContext的定义。
1、InvocationContext
Code
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
namespace Artech.SimpleAopFramework
{
/// <summary>
/// InvocationContext represents the context of a method invocation.
/// </summary>
public class InvocationContext
{
/// <summary>
/// A <see cref="IMethodCallMessage"/> object represents the method call.
/// </summary>
public IMethodCallMessage Request
{ get; set; }
/// <summary>
/// A <see cref="ReturnMessage"/> object represents the return of the method call.
/// </summary>
public ReturnMessage Reply
{ get; set; }
/// <summary>
/// A <see cref="IDictionary<object, object>"/> object used to set extra contextual information.
/// </summary>
public IDictionary<object, object> Properties
{ get; set; }
}
}
Request和Reply本质上都是一个System.Runtime.Remoting.Messaging.IMessage对象。Request是IMethodCallMessage 对象,表示方法调用的消息,Reply则是ReturnMessage对象,具有可以包含具体的返回值,也可以包含抛出的异常。Properties可以供我们自由地设置一些自定义的上下文。
2、ICallHandler、CallHandlerBase和HandlerAttribute
ICallHandler包含四个成员,PreInvoke和PostInvoke在执行目标方法前后被先后调用,自定义CallHandler可以根据自己的具体需求实现这个两个方法。PreInvoke返回值可以通过PostInvoke的correlationState获得。Ordinal表明CallHandler在CallHandler管道的位置,他决定了应用于同一个目标方法上的多个CallHandler的执行顺序。ReturnIfError表示CallHandler在抛出异常时是否直接退出。
Code
namespace Artech.SimpleAopFramework
{
/// <summary>
/// All of the custom call handler must direcly or indirectly impplement this interface.
/// </summary>
public interface ICallHandler
{
/// <summary>
/// This method will be injected into the invocation stack prior to invoke target method.
/// </summary>
/// <param name="context">A <see cref="InvocationContext"/> object indicating the current method invocation context.</param>
/// <returns>The user state which can be picked up in the PostInvoke method.</returns>
object PreInvoke(InvocationContext context);
/// <summary>
/// This method will be injected into the invocation stack after target method is invoked.
/// </summary>
/// <param name="context">A <see cref="InvocationContext"/> object indicating the current method invocation context.</param>
/// <param name="correlationState">The user state returned from the Preinvoke method.</param>
void PostInvoke(InvocationContext context, object correlationState);
/// <summary>
/// A <see cref="int"/> value indicating the position where the call handler is placed in the pipeline.
/// </summary>
int Ordinal
{ get; set; }
/// <summary>
/// A <see cref="bool"/> value indicating whether to directly return if encountering error.
/// </summary>
bool ReturnIfError
{ get; set; }
}
}
CallHandler的抽象基类CallHandlerBase仅仅是对ICallHandler的简单实现。
Code
namespace Artech.SimpleAopFramework
{
/// <summary>
/// The base class of all custom call handler.
/// </summary>
public abstract class CallHandlerBase : ICallHandler
{
#region ICallHandler Members
/// <summary>
/// The method will be injected into the invocation stack prior to execute the target method.
/// </summary>
/// <param name="context">A <see cref="InvocationContext"/> object presenting the current method invocation context.</param>
/// <returns>A state object which can be pick up by PostInvoke method.</returns>
public abstract object PreInvoke(InvocationContext context);
/// <summary>e
/// The method will be injected into the invication stack after the target method is invoked.
/// </summary>
/// <param name="context">A <see cref="InvocationContext"/> object presenting the current method invocation context.</param>
/// <param name="correlationState">A sate object returned from PreInvoke method.</param>
public abstract void PostInvoke(InvocationContext context, object correlationState);
/// <summary>
/// A <see cref="int"/> value indicating the position where the call handler is placed in the pipeline.
/// </summary>
public int Ordinal
{ get; set; }
/// <summary>
/// A <see cref="bool"/> value indicating whether to directly return if encountering error.
/// </summary>
public bool ReturnIfError
{ get; set; }
#endregion
}
}
HandlerAttribute中定义了CreateCallHandler方法创建相应的CallHandler对象,Ordinal和ReturnIfError同上。
Code
using System;
namespace Artech.SimpleAopFramework
{
/// <summary>
/// The base class of custom call handler attribute.
/// </summary>
public abstract class HandlerAttribute : Attribute
{
/// <summary>
/// All of the sub class must implement this method to create <see cref="ICallHandler"/> object.
/// </summary>
/// <returns>The created <see cref="ICallHandler"/> object</returns>
public abstract ICallHandler CreateCallHandler();
/// <summary>
/// A <see cref="int"/> value indicating the position where the call handler is placed in the pipeline.
/// </summary>
public int Ordinal
{ get; set; }
/// <summary>
/// A <see cref="bool"/> value indicating whether to directly return if encountering an error.
/// </summary>
public bool ReturnIfError
{ get; set; }
}
}
3、CallHandlerPipeline
CallHandlerPipeline是CallHandler的有序集合,我们通过一个IList<ICallHandler> 对象和代码最终目标对象的创建CallHandlerPipeline。CallHandlerPipeline的核心方法是Invoke。在Invoke方法中按照CallHandler在管道中的次序先执行PreInvoke方法,然后通过反射执行目标对象的相应方法,最后逐个执行CallHandler的PostInvoke方法。
Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
namespace Artech.SimpleAopFramework
{
public class CallHandlerPipeline
{
private object _target;
private IList<ICallHandler> _callHandlers;
public CallHandlerPipeline(object target)
: this(new List<ICallHandler>(), target)
{ }
public CallHandlerPipeline(IList<ICallHandler> callHandlers, object target)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
if (callHandlers == null)
{
throw new ArgumentNullException("callHandlers");
}
this._target = target;
this._callHandlers = callHandlers;
}
/// <summary>
/// Invoke the call handler and the target instane one by one.
/// </summary>
/// <param name="context"></param>
public void Invoke(InvocationContext context)
{
Stack<object> correlationStates = new Stack<object>();
Stack<ICallHandler> callHandlerStack = new Stack<ICallHandler>();
//Preinvoke.
foreach (ICallHandler callHandler in this._callHandlers)
{
correlationStates.Push(callHandler.PreInvoke(context));
if (context.Reply!= null && context.Reply.Exception != null && callHandler.ReturnIfError)
{
context.Reply=new ReturnMessage(context.Reply.Exception, context.Request);
return;
}
callHandlerStack.Push(callHandler);
}
//Invoke Target Object.
object[] copiedArgs = Array.CreateInstance(typeof(object),context.Request.Args.Length) as object[];
context.Request.Args.CopyTo(copiedArgs, 0);
try
{
object returnValue = context.Request.MethodBase.Invoke(this._target, copiedArgs);
context.Reply = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, context.Request.LogicalCallContext, context.Request);
}
catch (Exception ex)
{
context.Reply = new ReturnMessage(ex, context.Request);
}
//PostInvoke.
while (callHandlerStack.Count > 0)
{
ICallHandler callHandler = callHandlerStack.Pop();
object correlationState = correlationStates.Pop();
callHandler.PostInvoke(context, correlationState);
}
}
/// <summary>
/// Sort all of the call handler based on Orderinal.
/// </summary>
public void Sort()
{
ICallHandler[] callHandlers = this._callHandlers.ToArray<ICallHandler>();
ICallHandler swaper = null;
for (int i = 0; i < callHandlers.Length - 1; i++)
{
for (int j = i + 1; j < callHandlers.Length; j++)
{
if (callHandlers[i].Ordinal > callHandlers[j].Ordinal)
{
swaper = callHandlers[i];
callHandlers[i] = callHandlers[j];
callHandlers[j] = swaper;
}
}
}
this._callHandlers = callHandlers.ToList<ICallHandler>();
}
/// <summary>
/// Comine a new <see cref="CallHandlerPipeline"/> object.
/// </summary>
/// <param name="pipeline">The <see cref="CallHandlerPipeline"/> object to combine.</param>
public void Combine(CallHandlerPipeline pipeline)
{
if (pipeline == null)
{
throw new ArgumentNullException("pipeline");
}
foreach (ICallHandler callHandler in pipeline._callHandlers)
{
this.Add(callHandler);
}
}
/// <summary>
/// Combine a new <see cref="IList<ICallHandler>"/> object.
/// </summary>
/// <param name="callHandlers">The <see cref="IList<ICallHandler>"/> object to combine.</param>
public void Combine(IList<ICallHandler> callHandlers)
{
if (callHandlers == null)
{
throw new ArgumentNullException("callHandlers");
}
foreach (ICallHandler callHandler in callHandlers)
{
this.Add(callHandler);
}
}
/// <summary>
/// Add a new <see cref="ICallHandler"/> object to the call handler pipeline.
/// </summary>
/// <param name="callHandler">The <see cref="ICallHandler"/> object to add.</param>
/// <returns>The <see cref="ICallHandler"/> object to add.</returns>
public ICallHandler Add(ICallHandler callHandler)
{
if (callHandler == null)
{
throw new ArgumentNullException("callHandler");
}
this._callHandlers.Add(callHandler);
return callHandler;
}
}
}
4、InterceptionRealProxy<T>
InterceptingRealProxy<T>是现在AOP的关键所在,我们通过一个IDictionary<MemberInfo, CallHandlerPipeline>和目标对象创建InterceptingRealProxy对象。在Invoke方法中,根据方法表示方法调用的IMethodCallMessage对象的MethodBase为key,从CallHandlerPipeline字典中获得基于当前方法的CallHandlerPipeline,并调用它的Invoke方法,InvocationContext的Reply即为最终的返回。
Code
using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Reflection;
namespace Artech.SimpleAopFramework
{
/// <summary>
/// The InterceptingRealProxy is a custom <see cref="RealProxy"/> used to perform method interception.
/// </summary>
/// <typeparam name="T">The type representing the declaration type of the transparent proxy.</typeparam>
public class InterceptingRealProxy<T> : RealProxy
{
private IDictionary<MemberInfo, CallHandlerPipeline> _callHandlerPipelines;
public InterceptingRealProxy(object target, IDictionary<MemberInfo, CallHandlerPipeline> callHandlerPipelines)
: base(typeof(T))
{
if (callHandlerPipelines == null)
{
throw new ArgumentNullException("callHandlerPipelines");
}
this._callHandlerPipelines = callHandlerPipelines;
}
/// <summary>
/// The method invocaiton is intercepted and all of the related call handlers is invoked.
/// </summary>
/// <param name="msg">A <see cref="IMessage"/> representing method call.</param>
/// <returns>A <see cref="IMessage"/>representig the return of method invocation.</returns>
public override IMessage Invoke(IMessage msg)
{
InvocationContext context = new InvocationContext();
context.Request = (IMethodCallMessage)msg;
this._callHandlerPipelines[context.Request.MethodBase].Invoke(context);
return context.Reply;
}
}
}
5、InstanceBuidler
同PIAB通过PolicyInjection.Create()/Wrap()创建Transparent Proxy类型,InstanceBuidler也充当这样的工厂功能。InstanceBuidler的实现原理就是:通过反射获得目标类型上所有的HandlerAttribute,通过调用HandlerAttribute的CreateCallHandler创建相应的CallHandler。对于每个具体的方法,将应用在其类和方法上的所有的CallHandler组合成CallHandlerPipeline,然后以MemberInfo对象为Key将所有基于某个方法的CallHandlerPipeline构成一个CallHandlerPipeline字典。该字典,连同通过反射创建的目标对象,创建InterceptingRealProxy<T>对象。最后返回InterceptingRealProxy<T>对象的TransparentProxy对象。
Code
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Artech.SimpleAopFramework
{
/// <summary>
/// InstanceBuidler is used to create the object which can be intercepted.
/// </summary>
public class InstanceBuilder
{
/// <summary>
/// Create an object which can be intercepted.
/// </summary>
/// <typeparam name="TObject">The type of the target object.</typeparam>
/// <typeparam name="TInterface">The interface which the target type implements.</typeparam>
/// <returns>The created object of the interface type.</returns>
public static TInterface Create<TObject, TInterface>() where TObject:TInterface
{
TObject target = Activator.CreateInstance<TObject>();
InterceptingRealProxy<TInterface> realProxy = new InterceptingRealProxy<TInterface>(target, CreateCallHandlerPipeline<TObject,TInterface>(target));
return (TInterface)realProxy.GetTransparentProxy();
}
/// <summary>
/// Create an object which can be intercepted.
/// </summary>
/// <typeparam name="T">The type of target instance.</typeparam>
/// <returns>The created object which can be intercepted. </returns>
public static T Create<T>()
{
return Create<T, T>();
}
/// <summary>
/// Create a <see cref="CallHandlerPipeline"/> collection based on the target instance.
/// </summary>
/// <typeparam name="TObject">The type of target instance.</typeparam>
/// <typeparam name="TInterfce">The inteface which the target type implements.</typeparam>
/// <param name="target">The target instance.</param>
/// <returns>A <see cref="IDictionary<MemberInfo, CallHandlerPipeline>"/> representing the created call handler pipeline collection.</returns>
public static IDictionary<MemberInfo, CallHandlerPipeline> CreateCallHandlerPipeline<TObject, TInterfce>(TObject target)
{
CallHandlerPipeline pipeline = new CallHandlerPipeline(target);
object[] attributes = typeof(TObject).GetCustomAttributes(typeof(HandlerAttribute), true);
foreach (var attribute in attributes)
{
HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
pipeline.Add(handlerAttribute.CreateCallHandler());
}
IDictionary<MemberInfo, CallHandlerPipeline> kyedCallHandlerPipelines = new Dictionary<MemberInfo, CallHandlerPipeline>();
foreach (MethodInfo methodInfo in typeof(TObject).GetMethods())
{
MethodInfo declareMethodInfo = typeof(TInterfce).GetMethod(methodInfo.Name, BindingFlags.Public| BindingFlags.Instance);
if (declareMethodInfo == null)
{
continue;
}
kyedCallHandlerPipelines.Add(declareMethodInfo, new CallHandlerPipeline(target));
foreach (var attribute in methodInfo.GetCustomAttributes(typeof(HandlerAttribute),true))
{
HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
kyedCallHandlerPipelines[declareMethodInfo].Add(handlerAttribute.CreateCallHandler());
}
kyedCallHandlerPipelines[declareMethodInfo].Combine(pipeline);
kyedCallHandlerPipelines[declareMethodInfo].Sort();
}
return kyedCallHandlerPipelines;
}
}
}
四、如果创建自定义CallHandler
在一开始的例子中,我们创建了两个自定义的CallHandler,一个用于进行事务处理的TranactionScopeCallHandler,另一个用于异常处理的ExceptionCallHandler。我们现在就来简单谈谈它们的实现。
1、TranactionScopeCallHandler
先来看看TranactionScopeCallHandler和TranactionScopeCallHandlerAttribute。我们通过TranactionScope的方式实现事务支持。在PreInvoke方法中,创建并返回TranactionScope对象,在PostInvoke中,通过correlationState参数得到该TranactionScope对象,如果没有异常(context.Reply.Exception == null),调用Complete方法提交事务。最后调用Dispose释放TranactionScope对象。(TranactionScope具有一系列的属性,在这里为了简单起见,读采用默认值)
Code
using System.Transactions;
namespace Artech.SimpleAopFramework.Demos
{
public class TransactionScopeCallHandler : CallHandlerBase
{
public override object PreInvoke(InvocationContext context)
{
return new TransactionScope();
}
public override void PostInvoke(InvocationContext context, object correlationState)
{
TransactionScope transactionScope = (TransactionScope)correlationState;
if (context.Reply.Exception == null)
{
transactionScope.Complete();
}
transactionScope.Dispose();
}
}
public class TransactionScopeCallHandlerAttribute: HandlerAttribute
{
public override ICallHandler CreateCallHandler()
{
return new TransactionScopeCallHandler() { Ordinal = this.Ordinal, ReturnIfError = this.ReturnIfError };
}
}
}
2、ExceptionCallHandler
ExceptionCallHandler的MessageTemlate和Rethrow属性分别表示最终显示的错误信息模板,和是否需要将异常抛出来。由于异常处理发生在目标方法调用之后,所以异常处理逻辑实现在PostInvoke方法中。在这里,我仅仅将通过模板组装的出错消息打印出来而已。
Code
using System;
using System.Runtime.Remoting.Messaging;
namespace Artech.SimpleAopFramework.Demos
{
public class ExceptionCallHandler : CallHandlerBase
{
public string MessageTemplate
{ get; set; }
public bool Rethrow
{ get; set; }
public ExceptionCallHandler()
{
this.MessageTemplate = "{Message}";
}
public override object PreInvoke(InvocationContext context)
{
return null;
}
public override void PostInvoke(InvocationContext context, object correlationState)
{
if (context.Reply.Exception != null)
{
string message = this.MessageTemplate.Replace("{Message}", context.Reply.Exception.InnerException.Message)
.Replace("{Source}", context.Reply.Exception.InnerException.Source)
.Replace("{StackTrace}", context.Reply.Exception.InnerException.StackTrace)
.Replace("{HelpLink}", context.Reply.Exception.InnerException.HelpLink)
.Replace("{TargetSite}", context.Reply.Exception.InnerException.TargetSite.ToString());
Console.WriteLine(message);
if (!this.Rethrow)
{
context.Reply = new ReturnMessage(null, null, 0, context.Request.LogicalCallContext, context.Request);
}
}
}
}
public class ExceptionCallHandlerAttribute : HandlerAttribute
{
public string MessageTemplate
{ get; set; }
public bool Rethrow
{ get; set; }
public ExceptionCallHandlerAttribute()
{
this.MessageTemplate = "{Message}";
}
public override ICallHandler CreateCallHandler()
{
return new ExceptionCallHandler()
{
Ordinal = this.Ordinal,
Rethrow = this.Rethrow,
MessageTemplate = this.MessageTemplate,
ReturnIfError = this.ReturnIfError
};
}
}
}