Enterprise Library深入解析与灵活应用(6):自己动手创建迷你版AOP框架

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介:

基于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.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: }

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
4天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
RS-485网络中的标准端接与交流电端接应用解析
RS-485,作为一种广泛应用的差分信号传输标准,因其传输距离远、抗干扰能力强、支持多点通讯等优点,在工业自动化、智能建筑、交通运输等领域得到了广泛应用。在构建RS-485网络时,端接技术扮演着至关重要的角色,它直接影响到网络的信号完整性、稳定性和通信质量。
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
思通数科AI平台在尽职调查中的技术解析与应用
思通数科AI多模态能力平台结合OCR、NLP和深度学习技术,为IPO尽职调查、融资等重要交易环节提供智能化解决方案。平台自动识别、提取并分类海量文档,实现高效数据核验与合规性检查,显著提升审查速度和精准度,同时保障敏感信息管理和数据安全。
74 11
|
14天前
|
自然语言处理 并行计算 数据可视化
免费开源法律文档比对工具:技术解析与应用
这款免费开源的法律文档比对工具,利用先进的文本分析和自然语言处理技术,实现高效、精准的文档比对。核心功能包括文本差异检测、多格式支持、语义分析、批量处理及用户友好的可视化界面,广泛适用于法律行业的各类场景。
|
17天前
|
安全 编译器 PHP
PHP 8新特性解析与实践应用####
————探索PHP 8的创新功能及其在现代Web开发中的实际应用
|
18天前
|
机器学习/深度学习 人工智能 自然语言处理
医疗行业的语音识别技术解析:AI多模态能力平台的应用与架构
AI多模态能力平台通过语音识别技术,实现实时转录医患对话,自动生成结构化数据,提高医疗效率。平台具备强大的环境降噪、语音分离及自然语言处理能力,支持与医院系统无缝集成,广泛应用于门诊记录、多学科会诊和急诊场景,显著提升工作效率和数据准确性。
|
8天前
|
存储 供应链 物联网
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
|
8天前
|
存储 供应链 安全
深度解析区块链技术的核心原理与应用前景
深度解析区块链技术的核心原理与应用前景
16 0
|
12天前
|
SQL 监控 安全
员工上网行为监控软件:SQL 在数据查询监控中的应用解析
在数字化办公环境中,员工上网行为监控软件对企业网络安全和管理至关重要。通过 SQL 查询和分析数据库中的数据,企业可以精准了解员工的上网行为,包括基础查询、复杂条件查询、数据统计与分析等,从而提高网络管理和安全防护的效率。
25 0
|
15天前
|
前端开发 中间件 PHP
PHP框架深度解析:Laravel的魔力与实战应用####
【10月更文挑战第31天】 本文作为一篇技术深度好文,旨在揭开PHP领域璀璨明星——Laravel框架的神秘面纱。不同于常规摘要的概括性介绍,本文将直接以一段引人入胜的技术剖析开场,随后通过具体代码示例和实战案例,逐步引导读者领略Laravel在简化开发流程、提升代码质量及促进团队协作方面的卓越能力。无论你是PHP初学者渴望深入了解现代开发范式,还是经验丰富的开发者寻求优化项目架构的灵感,本文都将为你提供宝贵的见解与实践指导。 ####

推荐镜像

更多