zoukankan      html  css  js  c++  java
  • Unity 中的策略注入(转)

    (转自MSDN杂志:http://msdn.microsoft.com/zh-cn/magazine/gg598927.aspx)

    之前两篇文章中,我介绍了使用 Microsoft Unity 2.0 进行的面向方面的编程 (AOP)。AOP 成型于二十世纪九十年代,是为进一步改进和补充面向对象编程 (OOP) 而开发的编程技术,这种编程技术最近有所更新,并且得到许多控制反转 (IoC) 库的支持。 Unity 也不例外。 AOP 的主要目的是让开发人员更加有效地处理横切关注点。 AOP 在本质上是解决了下面的问题:在为某个应用程序设计对象模型的时候,您如何处理代码的安全、缓存或日志记录等方面? 这些方面对于实现十分重要,但并不严格属于您所构建的模型中的对象。 是否应在设计中大肆纳入业务之外的方面? 或者,利用其他方面来修饰面向业务的类是否会更好? 如果您选择后者,AOP 基本上可以提供相关语法来定义和附加这些方面。

    所谓“方面”,就是横切关注点的实现。 在方面的定义中,您需要指定一些事情。 首先,您需要为所实现的关注点提供代码。 在 AOP 术语中,这称为“建议”。 建议应用于某个特定的代码点,该代码点可以是方法主体、属性的 getter/setter,或者也可能是异常处理程序。 这个代码点称为“联接点”。 最后,在 AOP 术语中,还有一个称作“切入点”。 切入点是联接点的集合。 通常,切入点通过方法名称和通配符由条件进行定义。 最终,AOP 在运行时在联接点前后注入建议代码。 这样一个建议即与一个切入点建立关联。

    在之前的文章中,我讨论了 Unity 的拦截 API。 借助拦截 API 可以定义附加到类上的建议。 在 Unity 术语中,建议是一个行为对象。 通常,行为附加到通过 Unity 的 IoC 机制进行解析的某个类型上,不过拦截机制并不严格要求使用 IoC 功能。 实际上,您可以配置拦截,使之同样应用于通过纯代码创建的实例。

    行为通过一个实现固定接口(IInterceptionBehavior 接口)的类来体现。 该接口有一个名为 Invoke 的方法。通过重写此方法,您实际上定义了要在常规方法调用之前和/或之后所执行的步骤。 除了配置脚本,您也可以使用 Fluent 代码将行为附加到某个类型。 这样,您所要做的只是定义一个联接点。 那切入点呢?

    我们上个月讨论过,目标对象上所有被拦截的方法都依照行为对象的 Invoke 方法中所表达的逻辑执行。 基本拦截 API 不能区分不同方法,并且不支持特定的匹配规则。 要做到这一点,您可以求助于策略注入 API。

    策略注入和 PIAB

    如果您用过 Microsoft Enterprise Library (EntLib) 最新版本 5.0 之前的版本,那么您有可能听说过 Policy Injection Application Block (PIAB),并且您还可能在自己的某些应用程序中用过 PIAB。 EntLib 5.0 也有 PIAB 模块。 那么 Unity 策略注入与 EntLib PIAB 之间的区别何在?

    在 EntLib 5.0 中,PIAB 主要出于兼容方面的原因而存在。 在新版本中,PIAB 程序集的内容发生了变化。 具体而言,拦截的所有功能组件现在都已成为 Unity 的组成部分,并且先前 EntLib 版本中的所有系统提供的调用处理程序都已转移至其他程序集,如图 1 所示。

    图 1 Microsoft Enterprise Library 5.0 中对调用处理程序的重构

    调用处理程序 Enterprise Library 5.0 中的新程序集
    授权处理程序 Microsoft.Practices.EnterpriseLibrary.Security.dll
    缓存处理的处理程序 从 PIAB 中删除
    异常处理的处理程序 Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
    日志记录处理程序 Microsoft.Practices.EnterpriseLibrary.Logging.dll
    性能计数器处理程序 Microsoft.Practices.EnterpriseLibrary.PolicyInjection.dll
    验证处理程序 Microsoft.Practices.EnterpriseLibrary.Validation.dll

    图 1 所示,每个调用处理程序都已移至关联应用程序块的程序集。 因此,异常处理调用处理程序移至异常处理应用程序块,而验证处理程序移至验证应用程序块,依此类推。 其中唯一的例外是性能计数器处理程序,它移入了 PolicyInjection 程序集。 尽管程序集发生了变化,但类的命名空间仍然保持不变。 另外值得注意的是,出于安全方面的原因,之前位于 PIAB 中的缓存调用处理程序已从 EntLib 5.0 中删除,而只能从 EntLib Contrib CodePlex 网站获得:bit.ly/gIcP6H 这些变化的影响是 PIAB 现在由旧的组件组成,这些组件只是为了向后兼容而存在,并且仍需一些代码变更才能与版本 5.0 兼容。 除非您对旧版本依赖严重,否则建议您升级策略注入层,以便利用已融入 Unity 应用程序块的新的(且大体类似的)策略注入 API。 让我们深入探讨一下 Unity 中的策略注入。

    策略注入概览

    策略注入指的是一层代码,它扩展基本 Unity 拦截 API,以针对每个方法添加映射规则和调用处理程序。 策略注入实现为一种特别的拦截行为,分两个主要阶段:初始化和执行时。

    在初始化阶段,框架首先确定哪个可用策略可以应用于所拦截的目标方法。 在这里,策略是一组操作,可以按照特定顺序注入到所拦截的对象与它的实际调用方之间。 您只能拦截针对策略注入显式配置的对象上的方法(现有实例或新建的实例均可)。

    确定适用策略列表之后,策略注入框架开始准备操作管道(操作称为调用处理程序)。 管道通过组合为每个匹配策略而定义的所有处理程序而形成。 管道中的处理程序根据策略顺序进行排序,并且父策略中的每个处理程序都分配有优先级。 在调用某个启用策略的方法时,将处理之前构建的管道。 如果该方法转而调用同一对象上其他启用策略的方法,这些方法的处理程序管道将合并到主管道中。

    调用处理程序

    调用处理程序比“行为”更加具体,并且因为最初在 AOP 中定义而看起来与建议十分相似。 行为应用于类型,由您负责为不同的方法采取不同的操作,而调用处理程序则针对每个方法进行指定。

    调用处理程序形成于管道中,并按预先确定的顺序接受调用。 每个处理程序能够访问调用的详细信息,包括方法名称、参数、返回值和预期返回类型。 调用处理程序还可以修改参数和返回值、停止调用在管道中的传播以及引发异常。

    值得注意的是,Unity 并不附带提供任何调用处理程序。 您只能自己创建调用处理程序,或从 EntLib 5.0 引用应用程序块并使用图 1 中列出的任何调用处理程序。

    调用处理程序是一个实现 ICallHandler 接口的类,例如:

    1.  
    2.           public interface ICallHandler
    3. {
    4.   IMethodReturn Invoke(
    5.  
    6.     IMethodInvocation input, 
    7.  
    8.     GetNextHandlerDelegate getNext);
    9.   int Order { get; set; }
    10. }
    11.         

    Order 属性表示此处理程序相对于所有其他处理程序的优先级。 Invoke 方法返回一个包含该方法的任何返回值的类实例。

    调用处理程序只是执行自己的具体工作,然后释放管道,在这个意义上,调用处理程序的实现十分简单。 为了将控制权转交给管道中的下一个处理程序,处理程序会调用从 Unity 运行时收到的 getNext 参数。getNext 参数是一个委托,定义为:

    1.  
    2.           public delegate InvokeHandlerDelegate GetNextHandlerDelegate();
    3.         

    而 InvokeHandlerDelegate 定义为:

    1.  
    2.           public delegate IMethodReturn InvokeHandlerDelegate(
    3.  
    4.   IMethodInvocation input,
    5.  
    6.   GetNextHandlerDelegate getNext);
    7.         

    Unity 文档提供了一个阐述拦截的清晰图表。 图 2 中,您可以看到一个略加修改的图表,它表示了策略注入的体系结构。

    图 2 Unity 策略注入中的调用处理程序管道

    在系统提供的策略注入行为范围之内,您可以看到处理程序链用以处理对代理对象或派生类所调用的某个给定方法。 为了完整概述 Unity 中的策略注入,我们需要了解一下匹配规则。

    匹配规则

    通过匹配规则,您可以指定在哪里应用拦截逻辑。 如果您使用了行为,您的代码将会应用于整个对象;利用一条或更多匹配规则可以定义筛选器。 匹配规则表示用以选择特定对象和成员的条件,Unity 将为这些对象和成员附加处理程序管道。 用 AOP 术语来说,匹配规则就是用以定义切入点的条件。 图 3 列出了 Unity 提供本机支持的匹配规则。

    图 3 Unity 2.0 中受支持匹配规则的列表

    匹配规则 说明
    AssemblyMatchingRule 基于指定程序集中的类型选择目标对象。
    CustomAttributeMatchingRule 基于成员级别的自定义属性选择目标对象。
    MemberNameMatchingRule 基于成员名称选择目标对象。
    MethodSignatureMatchingRule 基于签名选择目标对象。
    NamespaceMatchingRule 基于命名空间选择目标对象。
    ParameterTypeMatchingRule 基于某个成员的某个参数的类型名称选择目标对象。
    PropertyMatchingRule 基于成员名称(包括通配符)选择目标对象。
    ReturnTypeMatchingRule 基于返回类型选择目标对象。
    TagMatchingRule 基于临时 Tag 属性的赋值选择目标对象。
    TypeMatchingRule 基于类型名称选择目标对象。

    匹配规则是实现 IMatchingRule 接口的类。 了解这一点之后,让我们来看看如何使用策略注入。 定义策略主要有三种方式:使用属性,使用 Fluent 代码,以及通过配置。

    通过属性添加策略

    图 4 是一个示例调用处理程序,它在某个操作得到负值结果时引发异常。 我会在不同情况下使用这个处理程序。

    图 4 NonNegativeCallHandler 类

    1.  
    2.           public class NonNegativeCallHandler : ICallHandler
    3. {
    4.   public IMethodReturn Invoke(IMethodInvocation input,
    5.                               GetNextHandlerDelegate getNext)
    6.   {
    7.     // Perform the operation
    8.     var methodReturn = getNext().Invoke(input, getNext);
    9.     // Method failed, go ahead
    10.     if (methodReturn.Exception != null)
    11.     return methodReturn;
    12.     // If the result is negative, then throw an exception
    13.     var result = (Int32) methodReturn.ReturnValue;
    14.     if (result <0)
    15.     {
    16.       var exception = new ArgumentException("...");
    17.       var response = input.CreateExceptionMethodReturn(exception);
    18.       // Return exception instead of original return value
    19.       return response;
    20.     }
    21.     return methodReturn;
    22.   }
    23.   public int Order { get; set; }
    24. }
    25.         

    使用处理程序最为简单的方式是在您所认为它可以发挥作用的位置将之附加至方法。 为此,您需要一个属性,例如:

    1.  
    2.           public class NonNegativeCallHandlerAttribute : HandlerAttribute
    3. {
    4.   public override ICallHandler CreateHandler(
    5.  
    6.     IUnityContainer container)
    7.   {
    8.     return new NonNegativeCallHandler();
    9.   }
    10. }
    11.         

    下面是一个示例 Calculator 类,为之附加了基于属性的策略:

    1.  
    2.           public class Calculator : ICalculator 
    3. {
    4.   public Int32 Sum(Int32 x, Int32 y)
    5.   {
    6.     return x + y;
    7.   }
    8.  
    9.   [NonNegativeCallHandler]
    10.   public Int32 Sub(Int32 x, Int32 y)
    11.   {
    12.     return x - y;
    13.   }
    14. }
    15.         

    其结果是不论返回值是什么,对于方法 Sum 的调用都会照常执行,而如有负数返回,对于方法 Sub 的调用则会引发异常。

    使用 Fluent 代码

    如果您不喜欢属性,也可以通过 Fluent API 来表达相同的逻辑。 这种情况下,在匹配规则方面须提供更多详细信息。 我们来看看如何表达这样一种想法:只在返回 Int32 并且名为 Sub 方法中注入代码。 我们使用 Fluent API 来配置 Unity 容器(参见图 5)。

    图 5 用以定义一组匹配规则的 Fluent 代码

    1.  
    2.           public static UnityContainer Initialize()
    3. {
    4.   // Creating the container
    5.   var container = new UnityContainer();
    6.   container.AddNewExtension<Interception>();
    7.  
    8.   // Adding type mappings
    9.   container.RegisterType<ICalculator, Calculator>(
    10.     new InterceptionBehavior<PolicyInjectionBehavior>(),
    11.     new Interceptor<TransparentProxyInterceptor>());
    12.  
    13.   // Policy injection
    14.   container.Configure<Interception>()
    15.     .AddPolicy("non-negative")
    16.     .AddMatchingRule<TypeMatchingRule>(
    17.       new InjectionConstructor(
    18.         new InjectionParameter(typeof(ICalculator))))
    19.     .AddMatchingRule<MemberNameMatchingRule>(
    20.       new InjectionConstructor(
    21.         new InjectionParameter(new[] {"Sub""Test"})))
    22.     .AddMatchingRule<ReturnTypeMatchingRule>(
    23.       new InjectionConstructor(
    24.         new InjectionParameter(typeof(Int32))))
    25.     .AddCallHandler<NonNegativeCallHandler>(
    26.       new ContainerControlledLifetimeManager(),
    27.         new InjectionConstructor());
    28.  
    29.   return container;
    30. }
    31.         

    请注意,如果您使用 ContainerControlledLifetimeManager 管理器,则所有方法必定会共享同一个调用处理程序实例。

    这段代码的效果是任何实现 ICalculator 的具体类型(即配置为接受拦截并通过 Unity 进行解析)都会选择两个潜在的注入候选对象:Sub 方法和 Test 方法。 然而,只有返回类型为 Int32 的方法才会继续匹配其他匹配规则。 这就是说,如果 Test 返回双精度值,它便会遭到排除。

    通过配置添加策略

    最后,可以通过配置文件表达同样的理念。 图 6 是 <unity> 节的预期内容。

    图 6 在配置文件中准备策略注入

    1.  
    2.           public class NonNegativeCallHandler : ICallHandler
    3. {
    4.   public IMethodReturn Invoke(IMethodInvocation input, 
    5.                               GetNextHandlerDelegate getNext)
    6.    {
    7.      // Perform the operation  
    8.      var methodReturn = getNext().Invoke(input, getNext);
    9.  
    10.      // Method failed, go ahead
    11.      if (methodReturn.Exception != null)
    12.        return methodReturn;
    13.  
    14.      // If the result is negative, then throw an exception
    15.      var result = (Int32) methodReturn.ReturnValue;
    16.      if (result <0)
    17.      {
    18.        var exception = new ArgumentException("...");
    19.        var response = input.CreateExceptionMethodReturn(exception);
    20.  
    21.        // Return exception instead of original return value
    22.        return response;   
    23.      }
    24.  
    25.      return methodReturn;
    26.    }
    27.  
    28.     public int Order { get; set; }
    29. }
    30. <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    31.   <assembly name="PolicyInjectionConfig"/>
    32.   <namespace name="PolicyInjectionConfig.Calc"/>
    33.   <namespace name="PolicyInjectionConfig.Handlers"/>
    34.  
    35.   <sectionExtension  ...
    36.           />
    37.  
    38.   <container>
    39.     <extension type="Interception" />
    40.  
    41.     <register type="ICalculator" mapTo="Calculator">
    42.       <interceptor type="TransparentProxyInterceptor" />
    43.       <interceptionBehavior type="PolicyInjectionBehavior" />
    44.     </register>
    45.  
    46.     <interception>
    47.       <policy name="non-negative">
    48.         <matchingRule name="rule1" 
    49.           type="TypeMatchingRule">
    50.           <constructor>
    51.              <param name="typeName" value="ICalculator" />
    52.           </constructor>
    53.         </matchingRule>
    54.         <matchingRule name="rule2" 
    55.           type="MemberNameMatchingRule">
    56.           <constructor>
    57.             <param name="namesToMatch">
    58.               <array type="string[]">
    59.                 <value value="Sub" />
    60.               </array>
    61.             </param>
    62.           </constructor>
    63.         </matchingRule>
    64.         <callHandler name="handler1" 
    65.           type="NonNegativeCallHandler">
    66.           <lifetime type="singleton" />
    67.         </callHandler>                    
    68.       </policy>
    69.     </interception>
    70.             
    71.   </container>
    72. </unity>
    73.         

    结果表明,如果在一个策略中有多个匹配规则,最终结果是对所有规则应用布尔运算符 AND(也就是说,全都必须为真)。 如果定义了多个策略,对于每个策略都会进行独立的匹配评估和处理程序应用。 因而,您可以从不同的策略应用处理程序。

    拦截概览

    总结一下,拦截是 Microsoft .NET Framework 空间中大多数 IoC 框架实现面向方面编程的一种方式。 通过拦截,您可以在任何特定程序集中任何特定类型的任何特定方法前后运行自己的代码。 以前,EntLib 提供了特定的应用程序块 PIAB 来执行这一工作。 在 EntLib 5.0 中,PIAB 的底层引擎已经转入 Unity,并且实现为 Unity 低级拦截 API 的一种特别行为(对于这种 API 在之前两期专栏中已有讨论)。 策略注入行为要求使用 Unity 容器,并且不仅限于通过低级拦截 API 使用。

    然而,低级拦截 API 不能用以选择您要拦截的类型成员,您必须自己编写代码来执行这一工作。 但利用策略注入行为,您可以将精力集中于所需行为的细节上面,而让库根据您所提供的规则来确定行为应用于哪些方法。

  • 相关阅读:
    SQL之CASE WHEN用法详解
    MySQL笔记汇总
    Linux常用命令
    TCP/IP速记
    数据结构和算法速记
    多线程相关概念
    线程安全&Java内存模型
    线程通讯wait&notify
    创建多线程的4种方式
    重写ThreadPoolTaskExecutor
  • 原文地址:https://www.cnblogs.com/chenjiang/p/3692508.html
Copyright © 2011-2022 走看看