zoukankan      html  css  js  c++  java
  • Enterprise Library深入解析与灵活应用(6):自己动手创建迷你版AOP框架

    基于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下载:https://files.cnblogs.com/artech/Artech.SimpleAopFramework.rar 

    一、如何使用?

    编程方式和PIAB基本上是一样的,根据具体的需求创建相应的CallHandler,通过Custom Attribute的形式将CallHandler应用到类型或者方法上面。下面就是一个简单例子。

       1: class Program
       2: {
       3:     static void Main(string[] args)
       4:     {
       5:         string userID = Guid.NewGuid().ToString();
       6:         InstanceBuilder.Create<UserManager, IUserManager>().CreateDuplicateUsers(userID, Guid.NewGuid().ToString());
       7:         Console.WriteLine("Is the user whose ID is \"{0}\" has been successfully created! {1}", userID, UserUtility.UserExists(userID) ? "Yes" : "No");
       8:     }
       9: }
      10:  
      11: public class UserManager : IUserManager
      12: {
      13:     [ExceptionCallHandler(Ordinal = 1, MessageTemplate = "Encounter error:\nMessage:{Message}")]
      14:     [TransactionScopeCallHandler(Ordinal = 2)]
      15:     public void CreateDuplicateUsers(string userID, string userName)
      16:     {
      17:         UserUtility.CreateUser(userID, userName);
      18:         UserUtility.CreateUser(userID, userName);
      19:     }
      20: }
      21: public interface IUserManager
      22: {
      23:     void CreateDuplicateUsers(string userID, string userName);
      24: }

    在上面例子中,我创建了两个CallHandler:TransactionScopeCallHandlerExceptionCallHandler,用于进行事务和异常的处理。也就是说,我们不需要手工地进行事务的Open、Commit和Rollback的操作,也不需要通过try/catch block进行手工的异常处理。为了验证正确性,我模拟了这样的场景:数据库中有一个用户表(Users)用于存储用户帐户,每个帐户具有唯一ID,现在我通过UserManager的CreateDuplicateUsers方法插入两个具有相同ID的记录,毫无疑问,如果没有事务的处理,第一次用户添加将会成功,第二次将会失败。反之如果我们添加的TransactionScopeCallHandler能够起作用,两次操作将在同一个事务中进行,重复的记录添加将会导致事务的回退。

    ExceptionCallHandler中,会对抛出的SqlException进行处理,在这我们仅仅是打印出异常相关的信息。至于具有要输出那些信息,可以通过ExceptionCallHandlerAttribute的MessageTemplate 属性定义一个输出的模板。运行程序,我们会得到这样的结果,充分证明了事务的存在,错误信息也按照我们希望的模板进行输出。

    image

    二、设计概要

    同PIAB的实现原理一样,我通过自定义RealProxy实现对CallHandler的执性,从而达到方法调用劫持的目的(底层具体的实现,可以参阅我的文章Policy Injection Application Block 设计和实现原理)。下面的UML列出了整个框架设计的所有类型。

    image

    • 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的定义。

    InvocationContext 

       1: public class InvocationContext
       2: {
       3:     public IMethodCallMessage Request
       4:     { get; set; }
       5:  
       6:     public ReturnMessage Reply
       7:     { get; set; }
       8:  
       9:     public IDictionary<object, object> Properties
      10:     { get; set; }
      11: }

    Request和Reply本质上都是一个System.Runtime.Remoting.Messaging.IMessage对象。Request是IMethodCallMessage 对象,表示方法调用的消息,Reply则是ReturnMessage对象,具有可以包含具体的返回值,也可以包含抛出的异常。Properties可以供我们自由地设置一些自定义的上下文。

    ICallHandler、CallHandlerBase和HandlerAttribute

    ICallHandler包含四个成员,PreInvoke和PostInvoke在执行目标方法前后被先后调用,自定义CallHandler可以根据自己的具体需求实现这个两个方法。PreInvoke返回值可以通过PostInvoke的correlationState获得。Ordinal表明CallHandler在CallHandler管道的位置,他决定了应用于同一个目标方法上的多个CallHandler的执行顺序。ReturnIfError表示CallHandler在抛出异常时是否直接退出。

       1: public interface ICallHandler
       2: {
       3:     object PreInvoke(InvocationContext context);
       4:  
       5:     void PostInvoke(InvocationContext context, object correlationState);
       6:  
       7:     int Ordinal{ get; set; }
       8:  
       9:     bool ReturnIfError{ get; set; }
      10: }

    CallHandler的抽象基类CallHandlerBase仅仅是对ICallHandler的简单实现。

       1: public abstract class CallHandlerBase : ICallHandler
       2: {
       3:     public abstract object PreInvoke(InvocationContext context);
       4:  
       5:     public abstract void PostInvoke(InvocationContext context, object correlationState);
       6:  
       7:     public int Ordinal{ get; set; }
       8:  
       9:     public bool ReturnIfError { get; set; }
      10: }

    HandlerAttribute中定义了CreateCallHandler方法创建相应的CallHandler对象,Ordinal和ReturnIfError同上。

       1: public abstract class HandlerAttribute : Attribute
       2: {
       3:     public abstract ICallHandler CreateCallHandler();
       4:  
       5:     public int Ordinal{ get; set; }
       6:  
       7:     public bool ReturnIfError{ get; set; }
       8: }

    CallHandlerPipeline

    CallHandlerPipeline是CallHandler的有序集合,我们通过一个IList<ICallHandler> 对象和代码最终目标对象的创建CallHandlerPipeline。CallHandlerPipeline的核心方法是Invoke。在Invoke方法中按照CallHandler在管道中的次序先执行PreInvoke方法,然后通过反射执行目标对象的相应方法,最后逐个执行CallHandler的PostInvoke方法。

       1: public class CallHandlerPipeline
       2: {
       3:     private object _target;
       4:     private IList<ICallHandler> _callHandlers;
       5:  
       6:     public CallHandlerPipeline(object target): this(new List<ICallHandler>(), target){ }
       7:  
       8:     public CallHandlerPipeline(IList<ICallHandler> callHandlers, object target)
       9:     {
      10:         if (target == null)
      11:         {
      12:             throw new ArgumentNullException("target");
      13:         }
      14:  
      15:         if (callHandlers == null)
      16:         {
      17:             throw new ArgumentNullException("callHandlers");
      18:         }
      19:  
      20:         this._target = target;
      21:         this._callHandlers = callHandlers;
      22:     }
      23:  
      24:     public void Invoke(InvocationContext context)
      25:     {
      26:         Stack<object> correlationStates = new Stack<object>();
      27:         Stack<ICallHandler> callHandlerStack = new Stack<ICallHandler>();
      28:  
      29:         //Preinvoke.
      30:         foreach (ICallHandler callHandler in this._callHandlers)
      31:         {
      32:             correlationStates.Push(callHandler.PreInvoke(context));
      33:             if (context.Reply != null && context.Reply.Exception != null && callHandler.ReturnIfError)
      34:             {
      35:                 context.Reply = new ReturnMessage(context.Reply.Exception, context.Request);
      36:                 return;
      37:             }
      38:             callHandlerStack.Push(callHandler);
      39:         }
      40:  
      41:         //Invoke Target Object.
      42:         object[] copiedArgs = Array.CreateInstance(typeof(object), context.Request.Args.Length) as object[];
      43:         context.Request.Args.CopyTo(copiedArgs, 0);
      44:         try
      45:         {
      46:             object returnValue = context.Request.MethodBase.Invoke(this._target, copiedArgs);
      47:             context.Reply = new ReturnMessage(returnValue, copiedArgs, copiedArgs.Length, context.Request.LogicalCallContext, context.Request);
      48:         }
      49:         catch (Exception ex)
      50:         {
      51:             context.Reply = new ReturnMessage(ex, context.Request);
      52:         }
      53:  
      54:         //PostInvoke.
      55:         while (callHandlerStack.Count > 0)
      56:         {
      57:             ICallHandler callHandler = callHandlerStack.Pop();
      58:             object correlationState = correlationStates.Pop();
      59:             callHandler.PostInvoke(context, correlationState);
      60:         }
      61:     }
      62:  
      63:     public void Sort()
      64:     {
      65:         ICallHandler[] callHandlers = this._callHandlers.ToArray<ICallHandler>();
      66:         ICallHandler swaper = null;
      67:         for (int i = 0; i < callHandlers.Length - 1; i++)
      68:         {
      69:             for (int j = i + 1; j < callHandlers.Length; j++)
      70:             {
      71:                 if (callHandlers[i].Ordinal > callHandlers[j].Ordinal)
      72:                 {
      73:                     swaper = callHandlers[i];
      74:                     callHandlers[i] = callHandlers[j];
      75:                     callHandlers[j] = swaper;
      76:                 }
      77:             }
      78:         }
      79:  
      80:         this._callHandlers = callHandlers.ToList<ICallHandler>();
      81:     }
      82:  
      83:     public void Combine(CallHandlerPipeline pipeline)
      84:     {
      85:         if (pipeline == null)
      86:         {
      87:             throw new ArgumentNullException("pipeline");
      88:         }
      89:  
      90:         foreach (ICallHandler callHandler in pipeline._callHandlers)
      91:         {
      92:             this.Add(callHandler);
      93:         }
      94:     }
      95:  
      96:     public void Combine(IList<ICallHandler> callHandlers)
      97:     {
      98:         if (callHandlers == null)
      99:         {
     100:             throw new ArgumentNullException("callHandlers");
     101:         }
     102:  
     103:         foreach (ICallHandler callHandler in callHandlers)
     104:         {
     105:             this.Add(callHandler);
     106:         }
     107:     }
     108:  
     109:     public ICallHandler Add(ICallHandler callHandler)
     110:     {
     111:         if (callHandler == null)
     112:         {
     113:             throw new ArgumentNullException("callHandler");
     114:         }
     115:  
     116:         this._callHandlers.Add(callHandler);
     117:         return callHandler;
     118:     }
     119: }

    InterceptionRealProxy<T>

    InterceptingRealProxy<T>是现在AOP的关键所在,我们通过一个IDictionary<MemberInfo, CallHandlerPipeline>和目标对象创建InterceptingRealProxy对象。在Invoke方法中,根据方法表示方法调用的IMethodCallMessage对象的MethodBase为key,从CallHandlerPipeline字典中获得基于当前方法的CallHandlerPipeline,并调用它的Invoke方法,InvocationContext的Reply即为最终的返回。

       1: public class InterceptingRealProxy<T> : RealProxy
       2: {
       3:     private IDictionary<MemberInfo, CallHandlerPipeline> _callHandlerPipelines;
       4:     public InterceptingRealProxy(object target, IDictionary<MemberInfo, CallHandlerPipeline> callHandlerPipelines)
       5:         : base(typeof(T))
       6:     {
       7:         if (callHandlerPipelines == null)
       8:         {
       9:             throw new ArgumentNullException("callHandlerPipelines");
      10:         }
      11:  
      12:         this._callHandlerPipelines = callHandlerPipelines;
      13:     }
      14:     
      15:     public override IMessage Invoke(IMessage msg)
      16:     {
      17:         InvocationContext context = new InvocationContext();
      18:         context.Request = (IMethodCallMessage)msg;
      19:         this._callHandlerPipelines[context.Request.MethodBase].Invoke(context);
      20:         return context.Reply;
      21:     }
      22: }

    InstanceBuidler

    同PIAB通过PolicyInjection.Create()/Wrap()创建Transparent Proxy类型,InstanceBuidler也充当这样的工厂功能。InstanceBuidler的实现原理就是:通过反射获得目标类型上所有的HandlerAttribute,通过调用HandlerAttribute的CreateCallHandler创建相应的CallHandler。对于每个具体的方法,将应用在其类和方法上的所有的CallHandler组合成CallHandlerPipeline,然后以MemberInfo对象为Key将所有基于某个方法的CallHandlerPipeline构成一个CallHandlerPipeline字典。该字典,连同通过反射创建的目标对象,创建InterceptingRealProxy<T>对象。最后返回InterceptingRealProxy<T>对象的TransparentProxy对象。

       1: public class InstanceBuilder
       2: {
       3:     public static TInterface Create<TObject, TInterface>() where TObject : TInterface
       4:     {
       5:         TObject target = Activator.CreateInstance<TObject>();
       6:         InterceptingRealProxy<TInterface> realProxy = new InterceptingRealProxy<TInterface>(target, CreateCallHandlerPipeline<TObject, TInterface>(target));
       7:         return (TInterface)realProxy.GetTransparentProxy();
       8:     }
       9:     
      10:     public static T Create<T>()
      11:     {
      12:         return Create<T, T>();
      13:     }
      14:     
      15:     public static IDictionary<MemberInfo, CallHandlerPipeline> CreateCallHandlerPipeline<TObject, TInterfce>(TObject target)
      16:     {
      17:         CallHandlerPipeline pipeline = new CallHandlerPipeline(target);
      18:         object[] attributes = typeof(TObject).GetCustomAttributes(typeof(HandlerAttribute), true);
      19:         foreach (var attribute in attributes)
      20:         {
      21:             HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
      22:             pipeline.Add(handlerAttribute.CreateCallHandler());
      23:         }
      24:  
      25:         IDictionary<MemberInfo, CallHandlerPipeline> kyedCallHandlerPipelines = new Dictionary<MemberInfo, CallHandlerPipeline>();
      26:  
      27:         foreach (MethodInfo methodInfo in typeof(TObject).GetMethods())
      28:         {
      29:             MethodInfo declareMethodInfo = typeof(TInterfce).GetMethod(methodInfo.Name, BindingFlags.Public | BindingFlags.Instance);
      30:             if (declareMethodInfo == null)
      31:             {
      32:                 continue;
      33:             }
      34:             kyedCallHandlerPipelines.Add(declareMethodInfo, new CallHandlerPipeline(target));
      35:             foreach (var attribute in methodInfo.GetCustomAttributes(typeof(HandlerAttribute), true))
      36:             {
      37:                 HandlerAttribute handlerAttribute = attribute as HandlerAttribute;
      38:                 kyedCallHandlerPipelines[declareMethodInfo].Add(handlerAttribute.CreateCallHandler());
      39:             }
      40:             kyedCallHandlerPipelines[declareMethodInfo].Combine(pipeline);
      41:             kyedCallHandlerPipelines[declareMethodInfo].Sort();
      42:         }
      43:  
      44:         return kyedCallHandlerPipelines;
      45:     }
      46: }

    四、如果创建自定义CallHandler

    在一开始的例子中,我们创建了两个自定义的CallHandler,一个用于进行事务处理的TranactionScopeCallHandler,另一个用于异常处理的ExceptionCallHandler。我们现在就来简单谈谈它们的实现。

    TranactionScopeCallHandler

    先来看看TranactionScopeCallHandler和TranactionScopeCallHandlerAttribute。我们通过TranactionScope的方式实现事务支持。在PreInvoke方法中,创建并返回TranactionScope对象,在PostInvoke中,通过correlationState参数得到该TranactionScope对象,如果没有异常(context.Reply.Exception == null),调用Complete方法提交事务。最后调用Dispose释放TranactionScope对象。(TranactionScope具有一系列的属性,在这里为了简单起见,读采用默认值)

       1: public class TransactionScopeCallHandler : CallHandlerBase
       2: {
       3:     public override object PreInvoke(InvocationContext context)
       4:     {
       5:         return new TransactionScope();
       6:     }
       7:  
       8:     public override void PostInvoke(InvocationContext context, object correlationState)
       9:     {
      10:         TransactionScope transactionScope = (TransactionScope)correlationState;
      11:         if (context.Reply.Exception == null)
      12:         {
      13:             transactionScope.Complete();
      14:         }
      15:         transactionScope.Dispose();
      16:     }
      17: }
      18:  
      19: public class TransactionScopeCallHandlerAttribute : HandlerAttribute
      20: {
      21:     public override ICallHandler CreateCallHandler()
      22:     {
      23:         return new TransactionScopeCallHandler() { Ordinal = this.Ordinal, ReturnIfError = this.ReturnIfError };
      24:     }
      25: }

    ExceptionCallHandler

    ExceptionCallHandler的MessageTemlate和Rethrow属性分别表示最终显示的错误信息模板,和是否需要将异常抛出来。由于异常处理发生在目标方法调用之后,所以异常处理逻辑实现在PostInvoke方法中。在这里,我仅仅将通过模板组装的出错消息打印出来而已。 

       1: public class ExceptionCallHandler : CallHandlerBase
       2: {
       3:     public string MessageTemplate{ get; set; }
       4:     public bool Rethrow{ get; set; }
       5:  
       6:     public ExceptionCallHandler()
       7:     {
       8:         this.MessageTemplate = "{Message}";
       9:     }
      10:  
      11:     public override object PreInvoke(InvocationContext context)
      12:     {
      13:         return null;
      14:     }
      15:  
      16:     public override void PostInvoke(InvocationContext context, object correlationState)
      17:     {
      18:         if (context.Reply.Exception != null)
      19:         {
      20:             string message = this.MessageTemplate.Replace("{Message}", context.Reply.Exception.InnerException.Message)
      21:                 .Replace("{Source}", context.Reply.Exception.InnerException.Source)
      22:                 .Replace("{StackTrace}", context.Reply.Exception.InnerException.StackTrace)
      23:                 .Replace("{HelpLink}", context.Reply.Exception.InnerException.HelpLink)
      24:                 .Replace("{TargetSite}", context.Reply.Exception.InnerException.TargetSite.ToString());
      25:             Console.WriteLine(message);
      26:             if (!this.Rethrow)
      27:             {
      28:                 context.Reply = new ReturnMessage(null, null, 0, context.Request.LogicalCallContext, context.Request);
      29:             }
      30:         }
      31:     }
      32: }
      33:  
      34: public class ExceptionCallHandlerAttribute : HandlerAttribute
      35: {
      36:  
      37:     public string MessageTemplate{ get; set; }
      38:  
      39:     public bool Rethrow{ get; set; }
      40:  
      41:     public ExceptionCallHandlerAttribute()
      42:     {
      43:         this.MessageTemplate = "{Message}";
      44:     }
      45:  
      46:     public override ICallHandler CreateCallHandler()
      47:     {
      48:         return new ExceptionCallHandler()
      49:         {
      50:             Ordinal = this.Ordinal,
      51:             Rethrow = this.Rethrow,
      52:             MessageTemplate = this.MessageTemplate,
      53:             ReturnIfError = this.ReturnIfError
      54:         };
      55:     }
      56: }
  • 相关阅读:
    简单工厂模式
    设计模式概述
    Excel中如何按单元格颜色求和,这五种牛批的方法,值得学习
    Excel数据透视表只能求和运算?快来学习求差运算小技巧
    如何在Excel中分组排名?两个公式轻松搞定!
    开始菜单之数字格式,这些基础知识还记得吗?
    2021,我来了
    2020年会必备,Excel轻松制作抽奖小游戏
    如何用Excel制作工作计划,跟踪任务进度,快来学习吧
    如何防止Excel数据录入出错,巧用数据验证,实现自动限制录入
  • 原文地址:https://www.cnblogs.com/artech/p/1342309.html
Copyright © 2011-2022 走看看