Dino Esposito

Unity 中的拦截功能在演示面向方面的编程 (AOP) 的核心概念之后,我介绍了一个具体的拦截示例,可能符合如今的很多开发人员的需要。

您是否希望围绕现有的代码再运行更多代码?

Unity 2.0 提供基于 Microsoft .NET Framework 4 的框架来实现此目的,而且极其快速和方便。

这个月我将回过头去更详细地讨论您通常会遇到的选择和选项。

Unity 中的 AOP

但如果您能顺利添加新的行为而不用触及现有的源代码,岂不是更好?

因此,您会由衷地喜欢可以让您顺利添加新的行为却无需触及源代码的解决方案。

在这种情况下,您同样期望能够顺利添加新的行为而不用触及源代码。

AOP 可以帮助您应对所有这些情况。

但这段简短的演示利用了几个假设。

首先,它利用由 Unity 反转控制 (IoC) 基础结构注册的类型,并通过 Unity 工厂层进行实例化。

基于接口的联接点集合表示只有该接口的成员才会通过代码注入在运行时扩展。

第三,我主要关注支持拦截的配置设置,而没有考虑能够让您在代码中配置 Unity 的 Fluent API。

在本文的其余部分,我将探讨 Fluent API 以及定义 Unity 拦截功能的其他方法。

可拦截的实例

换句话说,AOP 不是万能的,您不可能绑定通过标准的 new 运算符实例化的普通 CLR 类:

  1.           var calculator = new Calculator();
  2.         

当 AOP 和 IoC 一起使用时,就会得到顺利、轻松和有效的编码体验。

这里是一些基本代码,可以让现有的 Calculator 类实例变得可以拦截:

  1.           var calculator = new Calculator();
  2. var calculatorProxy = Intercept.ThroughProxy<ICalculator>(calculator,
  3.   new InterfaceInterceptor(), new[] { new LogBehavior() });
  4. Console.WriteLine(calculatorProxy.Sum(22));
  5.         

如果类派生自 MarshalByRefObject,那么拦截程序的类型必须是 TransparentProxyInterceptor:

  1.           var calculator = new Calculator();
  2. var calculatorProxy = Intercept.ThroughProxy(calculator,
  3.   new TransparentProxyInterceptor(), new[] { new LogBehavior() });
  4. Console.WriteLine(calculatorProxy.Sum(22));
  5.         

以下就是使用方法:

  1.           var calculatorProxy = Intercept.NewInstance<Calculator>(
  2.   new VirtualMethodInterceptor(), new[] { new LogBehavior() });
  3.         

那么 Unity 中有多少种拦截程序?

实例和类型拦截程序

拦截的类型有两种:实例拦截和类型拦截。

不用说,原始类型和派生类型的区别就在于用来筛选传入调用的逻辑。

对于实例拦截,应用程序代码首先使用传统的工厂(或 new 运算符)创建目标对象,然后强制通过 Unity 提供的代理与其交互。

实际的类型由 Unity 实时派生,并且会加入拦截逻辑(请参见图 1)。

Unity 中的拦截功能

图 1 实例拦截程序和类型拦截程序

VirtualMethodInterceptor 属于类型拦截程序类别。

该拦截程序可以应用到新的和现有的实例。

该拦截程序可以应用到新的和现有的实例。

该拦截程序只能应用到新的实例。

结果,任何可拦截操作都必须在创建实例之后。

请考虑使用以下代码:

  1.           var calculatorProxy = Intercept.NewInstance<Calculator>(
  2.   new VirtualMethodInterceptor(), new[] { new LogBehavior() });
  3.         

Calculator 类如下所示:


          public class Calculator {
  public virtual Int32 Sum(Int32 x, Int32 y) {
    return x + y;
  }
}
        

图 2 显示了对 calculatorProxy 变量进行动态检查后得到的类型的实际名称。

Unity 中的拦截功能

图 2 类型拦截之后的实际类型

当然,自我调用不需要经过代理,因此不会发生拦截。

使用 IoC 容器

但是,下面这个示例将使用 Unity 的容器以及基于代码的 Fluent 配置:

  1.           // Configure the IoC container
  2. var container = UnityStarter.Initialize();
  3.  
  4. // Start the application
  5. var calculator = container.Resolve<ICalculator>();
  6. var result = calculator.Sum(22);
  7.         

图 3 显示了启动代码可能的实现方式。

图 3 启动 Unity

  1.           public class UnityStarter {
  2.   public static UnityContainer Initialize() {
  3.     var container = new UnityContainer();
  4.  
  5.     // Enable interception in the current container 
  6.     container.AddNewExtension<Interception>();
  7.  
  8.     // Register ICalculator with the container and map it to 
  9.     // an actual type.
  10.           In addition, specify interception details.
  11.           container.RegisterType<ICalculator, Calculator>(
  12.       new Interceptor<VirtualMethodInterceptor>(),
  13.       new InterceptionBehavior<LogBehavior>());
  14.  
  15.     return container;
  16.   }
  17. }
  18.         

在这种情况下,启动代码包含以下两行:

  1.           var container = new UnityContainer();
  2. container.LoadConfiguration();
  3.         

这表示对接口公共成员的所有调用都将由 LogBehavior 和 BinaryBehavior 进行预处理和后处理。

图 4 通过配置添加拦截细节

  1.           <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
  2.   <assembly name="SimplestWithConfigIoC"/>
  3.   <namespace name="SimplestWithConfigIoC.Calc"/>
  4.   <namespace name="SimplestWithConfigIoC.Behaviors"/>
  5.  
  6.   <sectionExtension 
  7.     type="Microsoft.Practices.Unity.
  8.           InterceptionExtension.Configuration.
  9.           InterceptionConfigurationExtension,     
  10.       Microsoft.Practices.Unity.Interception.Configuration" />
  11.  
  12.   <container>
  13.     <extension type="Interception" />
  14.  
  15.     <register type="ICalculator" mapTo="Calculator">
  16.       <interceptor type="InterfaceInterceptor"/>
  17.       <interceptionBehavior type="LogBehavior"/>
  18.       <interceptionBehavior type="BinaryBehavior"/>
  19.     </register>
  20.  
  21.     <register type="LogBehavior">
  22.     </register>
  23.  
  24.     <register type="BinaryBehavior">
  25.     </register>
  26.  
  27.   </container>
  28. </unity>
  29.         

Unity 的默认设置会自动处理它们。

行为

行为甚至可以完全阻止方法被调用,或者多次调用方法。

如果它返回 False,行为就不会执行。

图 5 行为示例

  1.           public class BinaryBehavior : IInterceptionBehavior {
  2.   public IEnumerable<Type> GetRequiredInterfaces() {
  3.     return Type.EmptyTypes;
  4.   }
  5.  
  6.   public bool WillExecute {
  7.     get { return true; }
  8.   }
  9.  
  10.   public IMethodReturn Invoke(
  11.     IMethodInvocation input, 
  12.     GetNextInterceptionBehaviorDelegate getNext) {
  13.  
  14.     // Perform the operation
  15.     var methodReturn = getNext().Invoke(input, getNext);
  16.  
  17.     // Grab the output
  18.     var result = methodReturn.ReturnValue;
  19.  
  20.     // Transform
  21.     var binaryString = ((Int32)result).ToBinaryString();
  22.  
  23.     // For example, write it out
  24.     Console.WriteLine("Rendering {0} as binary = {1}"
  25.       result, binaryString);
  26.  
  27.     return methodReturn;
  28.   }
  29. }
  30.         

这实际上是在优化代理创建。

参数 getNext 是一个委托,用于移动到管道中下一个行为,并且最终执行目标上的方法。

请注意,目标对象上所有被拦截的方法都将按照 Invoke 中表达的逻辑执行。

使用我在本文中介绍的普通拦截,您能做的就是运行一组 IF 语句,来找出被调用的是哪个方法,如下所示:

  1.           if(input.MethodBase.Name == "Sum") {
  2.   ...
  3.           }
  4.         

下个月我将继续这个话题,探讨以更有效的方式应用拦截,为被拦截的方法定义匹配规则。

相关文章: