Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序

简介:

一、为什么CallHandler需要进行排序

PIAB为我们提供了一个很好地实现AOP的方式。AOP旨在实现Business LogicNon-Business Infrastructure Logic的分离。通过PIAB,我们将这些业务无关的逻辑定义在一个个的CallHandler中,然后通过Attribute或者Configuration的方式,将我们所需的CallHandler运用到相应的目标对象中。从这个意义上讲,PIAB具有很好的FlexibilityExtensibility。但是,就我看来PIAB也具有一些不足之处,其最大的局限性在于:不能控制运用到某个Method的多个方法的执行顺序。而让CallHandler按照我们希望的顺序进行调用是非常有必要的。

举个例子,假设我们将以下3CallHandler运用到某个方法中:

  • ValidationHandler:用于参数参数的验证,比如是否为null, stringLength是否超出长度等等。
  • TransactionEnlistHandler: 用于将操作自动纳入到一个Transaction中,从而保证数据的一致性。
  • AuditLoggingHandler:当时操作成功执行后进行Audit Log

很显然,正常的执行顺序应该是这样的:在最开始调用ValidationHandler进行参数的验证;Audit Log需要和目标方法一起纳入同一个Transaction中,所以TransactionEnlistHandler的调用紧随其后,最后才是AuditLoggingHandler

Microsoft提供的原生的PIAB是无法实现的,好在Enterprise Library是开源的,我们可以修改PIABSource Code来使其实现我们的目标。而仅仅是一个很小的改动。接下来我们就来讨论一下如何来实现可被排序的CallHandler Pipeline

二、如何创建Sequential CallHandler Pipeline

如果要了解我们这个Sequential CallHandler Pipeline的实现,需要对PIAB的是实现机制有一定的了解。在本系列的第二部分里,我对PIAB的实现机制进行了详细的阐述,在这里我仅仅简单介绍一个PIAB是如何实现AOP的。

PIABAOP的实现原理可以用一个词来概括:Method Interception。具体的做法做法是:通过PIAB Factory创建基于Target TypeReal Proxy,然后通过这个Real Proxy创建Transparent Proxy,并通过该Transparent Proxy调用Target Instance。在创建Real Proxy中,将运用到该Type的所有CallHandler缓存起来。当进行调用的时候,Transparent Proxy调用Real ProxyInvoke方法。在该方法中,在将运用到当前MethodCallHandler构成一个Handler Pipeline。在真正调用Target Instance之前,按照Pipeline的先后顺序依次调用每个CallHandler

而我们实现的切入点就是:CallHandler Pipeline创建之后,再根据我们希望的顺序将所有的CallHander重新排序

三、Sequential CallHandler Pipeline的实现

实现一个Sequential CallHandler Pipeline的一个前提就是,如何确定一个CallHandlerPipeline的位置。为此,我们需要我们的Custom CallHandler有一个额外的属性:Ordinal,表明在Pipeline的序号,序号小的在前,大的在后。如何没有该属性,比如PIAB提供的所有CallHandler,我们将其放在最后。

我们仅仅需要修改两个PIAB Class: Microsoft.Practices.EnterpriseLibrary.PolicyInjection. HandlerPipelineMicrosoft.Practices.EnterpriseLibrary.PolicyInjection. RemotingInterception. InterceptingRealProxy

对于HandlerPipeline,添加了一个新的PropertyHandlers,用于在InterceptingRealProxy中能够获得组成Pipeline的所有CallHandler以利于排序。

   1: public class HandlerPipeline
   2: {
   3:  
   4:     private List<ICallHandler> handlers;
   5:     public List<ICallHandler> Handlers
   6:     {
   7:         get { return handlers; }
   8:         set { handlers = value; }
   9:     }
  10: }

现在我们添加一个新的方法:ResortHandlers,将所有CallHandler按照Ordinal的大小进行重新排序(通过Reflection得到Ordinal的值)。

   1: public HandlerPipeline ResortHandlers(HandlerPipeline pipeline)
   2: {
   3:     HandlerPipeline sequentialPipeline = new HandlerPipeline();
   4:     IDictionary<ICallHandler, int> handlerOrdinalPairList = new Dictionary<ICallHandler, int>();
   5:     ICallHandler[] handlers = Array.CreateInstance(typeof(ICallHandler), pipeline.Handlers.Count) as ICallHandler[];
   6:     int[] ordinals = Array.CreateInstance(typeof(int), pipeline.Handlers.Count) as int[];
   7:     for (int i = 0; i < pipeline.Handlers.Count; i++)
   8:     {
   9:         ICallHandler handler = pipeline.Handlers[i];
  10:         handlers[i] = handler;
  11:         Type handlerType = handler.GetType();
  12:         MemberInfo[] memberInfos = handlerType.GetMember("Ordinal");
  13:         if (memberInfos.Length == 0)
  14:         {
  15:             ordinals[i] = int.MaxValue;
  16:             continue;
  17:         }
  18:         PropertyInfo propertyInfo = memberInfos[0] as PropertyInfo;
  19:         if (propertyInfo == null)
  20:         {
  21:             ordinals[i] = int.MaxValue;
  22:             continue;
  23:         }
  24:  
  25:         int ordinal = (int)propertyInfo.GetValue(handler, null);
  26:         ordinals[i] = ordinal;
  27:     }
  28:  
  29:     ICallHandler swapHandler;
  30:     int swapOrdinal;
  31:     for (int i = 0; i < pipeline.Handlers.Count - 1; i++)
  32:     {
  33:         for (int j = i + 1; j < pipeline.Handlers.Count; j++)
  34:         {
  35:  
  36:             if (ordinals[i] > ordinals[j])
  37:             {
  38:                 swapOrdinal = ordinals[i];
  39:                 ordinals[i] = ordinals[j];
  40:                 ordinals[j] = swapOrdinal;
  41:                 swapHandler = handlers[i];
  42:                 handlers[i] = handlers[j];
  43:                 handlers[j] = swapHandler;
  44:             }
  45:         }
  46:     }
  47:     return new HandlerPipeline(handlers);
  48: }

注:采用Reflection的方式获得Ordinal并不是一种很好的方式,最好是定义一个Abstract CallHandler BaseClass,并将Ordinal Property定义在这个BaseClass中。

该方法将在OrdinalInvoke中调用:

   1: public override IMessage Invoke(IMessage msg)
   2: {
   3:     IMethodCallMessage callMessage = (IMethodCallMessage)msg;
   4:     HandlerPipeline pipeline;
   5:     if (memberHandlers.ContainsKey(callMessage.MethodBase))
   6:     {
   7:         pipeline = memberHandlers[callMessage.MethodBase];
   8:         //Added by Jiang Jin Nan
   9:         pipeline = ResortHandlers(pipeline);
  10:     }
  11:     else
  12:     {
  13:         pipeline = new HandlerPipeline();
  14:     }
  15:  
  16:     RemotingMethodInvocation invocation = new RemotingMethodInvocation(callMessage, target);
  17:     IMethodReturn result =
  18:         pipeline.Invoke(invocation, delegate(IMethodInvocation input, GetNextHandlerDelegate getNext)
  19:             {
  20:                 try
  21:                 {
  22:                     object returnValue = callMessage.MethodBase.Invoke(target, invocation.Arguments);
  23:                     return input.CreateMethodReturn(returnValue, invocation.Arguments);
  24:                 }
  25:                 catch (TargetInvocationException ex)
  26:                 {
  27:                     return input.CreateExceptionMethodReturn(ex.InnerException);
  28:                 }
  29:  
  30:             });
  31:  
  32:     return ((RemotingMethodReturn)result).ToMethodReturnMessage();
  33: }

这就是所有需要的改动,为了验证是否有效,我们照例写一个测试程序。

四、如何使用Sequential CallHandlerPIAB

为了验证我们上所做的能否实现我们的目标:让运用到某个Method上的CallHandler按照我们希望的顺序来执行,我们创建了两个Custom CallHandler: CustomHandlerA CustomHandlerB

   1: namespace Artech.SequentialCallHandlers
   2: {
   3:     public class CustomHandlerA : ICallHandler   
   4:     {
   5:         public int Ordinal{ get; set; }
   6:         public CustomHandlerA()
   7:         {
   8:             this.Ordinal = int.MaxValue;
   9:         }
  10:         public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
  11:         {
  12:             Console.WriteLine("Artech.SequentialCallHandlers.CustomHandlerA is invoked!");
  13:             return getNext()(input, getNext);
  14:         }
  15:     }
  16: }

   1: namespace Artech.SequentialCallHandlers
   2: {
   3:     public class CustomHandlerB : ICallHandler
   4:     {
   5:         public int Ordinal{ get; set; }
   6:         public CustomHandlerB()
   7:         {
   8:             this.Ordinal = int.MaxValue;
   9:         }
  10:  
  11:         public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
  12:         {
  13:             Console.WriteLine("Artech.SequentialCallHandlers.CustomHandlerB is invoked!");
  14:             return getNext()(input, getNext);
  15:         }
  16:     }
  17: }

 下面是两个对应的HandlerAttribute

   1: namespace Artech.SequentialCallHandlers
   2: {
   3:     public class ACustomHandlerAttribute : HandlerAttribute
   4:     {
   5:         public int Ordinal{ get; set; }
   6:         public override ICallHandler CreateHandler()
   7:         {
   8:             return new CustomHandlerA() { Ordinal = this.Ordinal };
   9:         }
  10:     }
  11: }

   1: namespace Artech.SequentialCallHandlers
   2: {
   3:     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
   4:     public class BCustomHandlerAttribute : HandlerAttribute
   5:     {
   6:         public int Ordinal{ get; set; }
   7:         public override ICallHandler CreateHandler()
   8:         {
   9:             return new CustomHandlerB() { Ordinal = this.Ordinal };
  10:         }
  11:     }
  12: }

注:如何定义Custom CallHandler,在本系列的第三部分有详细的介绍。

然后,我们将这连个Attribute运用到同一个方法中:

   1: class PolicyInjectionType : MarshalByRefObject
   2: {
   3:     [BCustomHandlerAttribute(Ordinal = 1)]
   4:     [ACustomHandlerAttribute(Ordinal = 2)]
   5:     public void DoSomething()
   6:     {
   7:         Console.WriteLine("The target object is invoked!");
   8:     }
   9: }

我们在一个Console ApplicationMain()种调用这个DoSomething()方法:

   1: class Program
   2: {
   3:     static void Main(string[] args)
   4:     {
   5:         PolicyInjectionType proxy = PolicyInjection.Create<PolicyInjectionType>();
   6:         proxy.DoSomething();
   7:     }
   8: }

由于CustomHandlerAOrdinal2CustomHandlerBOrdinal1,所以他们正确的执行顺序为:CustomHandlerB-CustomHandlerA。输出的结果证实了这一点:


我们来改变一下他们的顺序:

   1: class PolicyInjectionType : MarshalByRefObject
   2: {
   3:     [BCustomHandlerAttribute(Ordinal = 2)]
   4:     [ACustomHandlerAttribute(Ordinal = 1)]
   5:     public void DoSomething()
   6:     {
   7:         Console.WriteLine("The target object is invoked!");
   8:     }
   9: }

这样的话,两个CallHandler的顺序将变成:CustomHandlerA-CustomHandlerB。我们再来看看输出的结果:

EnterLib PIAB系列:

Enterprise Library Policy Injection Application Block 之一: PIAB Overview
Enterprise Library Policy Injection Application Block 之二: PIAB设计和实现原理
Enterprise Library Policy Injection Application Block 之三: PIAB的扩展—创建自定义CallHandler(提供Source Code下载)
Enterprise Library Policy Injection Application Block 之四:如何控制CallHandler的执行顺序


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
4月前
|
XML 缓存 API
【Azure API 管理】使用APIM进行XML内容读取时遇见的诡异错误 Expression evaluation failed. Object reference not set to an instance of an object.
【Azure API 管理】使用APIM进行XML内容读取时遇见的诡异错误 Expression evaluation failed. Object reference not set to an instance of an object.
SAP WM初阶Interim Storage Type不好启用Storage Unit Management
SAP WM初阶Interim Storage Type不好启用Storage Unit Management
SAP WM初阶Interim Storage Type不好启用Storage Unit Management
|
SQL 数据库
CMU 15-721 16-服务器端的逻辑执行 Server -side Logic Execution
今日议题 背景介绍 用户自定义函数的内联 背景介绍 数据库客户端API 目前我们假设所有的用户逻辑都在客户自己的应用中,然后通过客户端协议如JDBC/ODBC和数据库进行通信获取和存储数据,如下图: 嵌入式数据库逻辑 数据库允许将应用逻辑移植到数据库中减少网络通信的交互次数,这样的好处是高效和可重用。
1300 0
|
Kubernetes 调度 Windows
K8s 1.14 发布了,Release Note 该怎么读?
在本篇文章中,我们将 1.14 的Release Note 按照主题进行了重新归纳和梳理,按照类别对重要变更进行了技术剖析和讨论。希望这种“分类解读”的方式,能够帮助大家更好的理解 1.14 这个发布的核心内容。
1263 0