利用Unity进行AOP编程: 拦截器实战(二)

前言

上一篇文章里,我们介绍了Unity拦截器的基本概念,本编文章我们将利用它来进行实战。

在Unity Container中配置

默认情况下,Unity Container不支持拦截器,因此我们得显式地将其加入到项目中,当然,首先得通过NuGet安装扩展包。

1
2
3
4
5
using Microsoft.Practices.Unity.InterceptionExtension;
...

IUnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();

定义拦截器

上篇文章,我们用装饰器模式实现了logging和caching两种横切关系,而在这里,我们通过实现IInterceptionBehavior接口来实现拦截。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using Microsoft.Practices.Unity.InterceptionExtension;

class LoggingInterceptionBehavior : IInterceptionBehavior
{
  public IMethodReturn Invoke(IMethodInvocation input,
    GetNextInterceptionBehaviorDelegate getNext)
  {
    // Before invoking the method on the original target.
    WriteLog(String.Format(
      "Invoking method {0} at {1}",
      input.MethodBase, DateTime.Now.ToLongTimeString()));

    // Invoke the next behavior in the chain.
    var result = getNext()(input, getNext);

    // After invoking the method on the original target.
    if (result.Exception != null)
    {
      WriteLog(String.Format(
        "Method {0} threw exception {1} at {2}",
        input.MethodBase, result.Exception.Message,
        DateTime.Now.ToLongTimeString()));
    }
    else
    {
      WriteLog(String.Format(
        "Method {0} returned {1} at {2}",
        input.MethodBase, result.ReturnValue,
        DateTime.Now.ToLongTimeString()));
    }
    
    return result;
  }

  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public bool WillExecute
  {
    get { return true; }
  }

  private void WriteLog(string message)
  {
    ...
  }
}

IInterceptionBehavior接口定义了三个方法:WillExecute, GetRequiredInterface以及Invoke。在大多数场景下,我们可以使用WillExecuteGetRequiredInterface的默认实现。WillExecute属性允许我们通过定义该行为是否应该执行来优化行为链; 在示例代码中,这个属性永远返回true,因此这个拦截行为将始终执行。GetRequiredInterface方法允许我们指定关联到改拦截行为的接口类型。在示例中,我们将在拦截器注册时指定接口类型,因此这个方法返回Type.EmptyTypes

Invoke方法接受2个参数:input包含了调用的客户对象的方法名称和参数值的信息,getNext是一个委托,定义了拦截管道(或者代理对象)中要调用的下一个拦截行为。在示例中,我们可以看到该拦截行为如何在下一个拦截行为之前获取到客户对象调用的方法名,然后记录方法调用的详细信息。接着它又调用下一个拦截行为,并且记录其是否调用成功。最终,它将调用结果返回给管道中的前一个拦截行为。

以下的代码展示了更复杂的caching拦截行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using Microsoft.Practices.Unity.InterceptionExtension;

class CachingInterceptionBehavior : IInterceptionBehavior
{
  public IMethodReturn Invoke(IMethodInvocation input,
    GetNextInterceptionBehaviorDelegate getNext)
  {
    
    // Before invoking the method on the original target.
    if (input.MethodBase.Name == "GetTenant")
    {
      var tenantName = input.Arguments["tenant"].ToString();
      if (IsInCache(tenantName))
      {
        return input.CreateMethodReturn(
          FetchFromCache(tenantName));
      }
    }

    IMethodReturn result = getNext()(input, getNext);

    // After invoking the method on the original target.
    if (input.MethodBase.Name == "SaveTenant")
    {
      AddToCache(input.Arguments["tenant"]);
    }

    return result;

  }

  public IEnumerable<Type> GetRequiredInterfaces()
  {
    return Type.EmptyTypes;
  }

  public bool WillExecute
  {
    get { return true; }
  }

  private bool IsInCache(string key) {...}

  private object FetchFromCache(string key) {...}

  private void AddToCache(object item) {...}
}

代码中可以看出,这个拦截行为过滤出名为GetTenant的方法,接着尝试从缓存中获取相关的tenant。假如它在缓存中找到对应的tenant,就没必要继续调用GetTenant这个方法来获取tenant,而是直接使用CreateMethodReturn这个方法从缓存中将tenant返回给管道中的前一个拦截行为。

同样地,该行为在调用了下一个拦截行为之后过滤出SaveTenant方法 ,并把tenant对象的副本放入了缓存中。

这里我们简单地过滤出方法名称,后面我们将展示如何利用策略配置来聪明地做到这一点。

注册拦截器

在完成了CachingIntercptionBehaviorLoggingInterceptionBehavior之后,我们需要将其注册到Unity容器:

1
2
3
4
5
6
7
8
9
using Microsoft.Practices.Unity.InterceptionExtension;

...

container.AddNewExtension<Interception>();
container.RegisterType<ITenantStore, TenantStore>(
  new Interceptor<InterfaceInterceptor>(),
  new InterceptionBehavior<LoggingInterceptionBehavior>(),
  new InterceptionBehavior<CachingInterceptionBehavior>());

RegisterType方法的第一个参数——Interceptor对象,定义了拦截器的类型,另外两个参数则注册了TenantStore的两个拦截行为。

InterfaceInterception定义了这个拦截器基于代理对象。我们同样可以使用TransparentProxyInterceptorVirtualMethodInterception这两个拦截器类型,之后再作详细介绍。

拦截行为的注册顺序将决定它们在管道中的执行顺序。在该示例中,我们一定不能把顺序弄错,因为当caching拦截行为在缓存中找到tenant后,就不把客户对象的调用请求传递给下一个拦截行为了,假如搞错了顺序且在缓存中找到了tenant,你就不会记录日志了。

使用拦截器

最后一步就是如何在运行时使用拦截器了,很简单,我们只需要调用相关方法就行了,logging和caching拦截将会自动附加上去:

1
2
var tenantStore = container.Resolve<ITenantStore>();
tenantStore.SaveTenant(tenant);

要注意的是,上述tanantStore变量并非是TenantStore类型的,而是一个动态创建、实现了ITenantStore接口的代理对象,这个代理对象包含了ITenantStore接口定义的方法、属性和事件。上一篇文章的实例拦截介绍了这一点。

总结

这篇文章向大家介绍了Unity拦截器在项目中基础的实例拦截实战,下篇文章将给大家介绍其他更加灵活的拦截方式。