面向方面的编程

使用 RealProxy 类进行面向方面的编程

Bruno Sonnino

一个架构良好的应用程序有不同的层,这样,不同的关注点不会进行不必要的交互。假设要设计松散耦合、可维护的应用程序,但在开发过程中,发现某些要求可能不适合体系结构,如:

  • 在将数据写入数据库之前,必须对数据进行验证。
  • 应用程序必须具备审计和日志记录功能,以进行合理的操作。
  • 应用程序必须维护调试日志以检查操作是否正常。
  • 必须测量某些操作的性能,以便了解这些操作是否在要求的范围内。

所有这些要求都需要大量的工作以及代码重复。您必须在系统的很多部分添加相同的代码,这样就不符合“切勿重复”(DRY) 的原则,维护也更加困难。如果要求有任何变化,都会引起对程序的大量更改。如果我必须在应用程序中添加这类内容,我会想:“为什么编译器不能为我在多个位置添加这些重复代码?”,或者“我希望我可以‘向这个方法添加日志记录’”。

值得高兴的是,确实可以做到这一点:面向方面的编程 (AOP)。它从跨对象或层的边界的方面分离出常规代码。例如,应用程序日志不绑定到任何应用程序层。它应用于整个程序,应该无所不在。这称为“横切关注点”。

根据维基百科,AOP 是“旨在通过允许分离横切关注点来提高模块化程度的编程模式”。它处理发生在系统多个部分的功能,将这种功能与应用程序核心分开,从而改进关注点的分离,避免代码重复和耦合。

本文将介绍 AOP 基础知识,然后详细说明如何通过 Microsoft .NET Framework 类 RealProxy 使用动态代理来简化这一过程。

实现 AOP

AOP 的最大优势是,您只需关注一个位置的特定方面,对其进行编程,根据需要将其应用于所有位置。AOP 有许多用途,如:

  • 在应用程序中实现日志记录。
  • 在操作之前使用身份验证(如仅允许经过身份验证的用户执行某些操作)。
  • 为属性 setter 实现验证或通知(为实现 INotifyPropertyChanged 接口的类更改属性时,调用 PropertyChanged 事件)。
  • 更改某些方法的行为。

可以看到,AOP 有许多用途,但您使用它时必须小心。它会隐藏一些代码,而代码是存在的,在相关方面的每次调用中运行。它可能会有错误,严重影响应用程序的性能。方面中的细微错误可能要耗费很多调试时间。如果方面不在很多位置使用,有时最好是直接添加到代码中。

AOP 实现会采用一些常见方法:

  • 使用预处理器(如 C++ 中的预处理器)添加源代码。
  • 使用后处理器在编译后的二进制代码上添加指令。
  • 使用特殊编译器在编译时添加代码。
  • 在运行时使用代码拦截器拦截执行并添加所需的代码。

在 .NET Framework 中,最常用的方法是后处理和代码拦截。PostSharp (postsharp.net) 使用前一方法,Castle DynamicProxy (bit.ly/JzE631) 和 Unity (unity.codeplex.com) 等依赖关系注入容器使用后一方法。这些工具通常使用称为 Decorator 或 Proxy 的设计模式来执行代码拦截。

Decorator 设计模式

Decorator 设计模式解决一个常见问题:您有一个类,需要向其添加某种功能。您有几种选择:

  • 可以将新功能直接添加到类。但是,这样类要多承担一项责任,不符合“单一责任”原则。
  • 您可以创建新类来执行这一功能,然后从旧类调用新类。这会带来新问题:要是还需要使用不含新功能的类,该怎么办?
  • 您可以继承一个新类,添加新功能,但这样可能产生许多新的类。例如,假设您有一个用于创建、读取、更新和删除 (CRUD) 数据库操作的存储库类,您需要添加审计。后来,您需要添加数据验证以确保数据正确更新。此后,您可能还需要对访问进行身份验证,以确保只有授权用户才能访问这些类。以下是较大的问题:您可以用一些类来实现所有三个方面,可以用一些类仅实现其中两个方面甚至仅一个方面。最后,会有多少类?
  • 您可以使用方面“修饰”类,从而创建一个使用方面、然后调用旧类的新类。这样,如果需要一个方面,就对它修饰一次。如果需要两个方面,就对它修饰两次,依此类推。假设您订购一个玩具(我们都是电脑高手,可以是 Xbox 或智能手机这类玩具)。它需要包装,以便在商店中展示,也可以得到保护。您订购时配上礼品包装(第二次装饰),用胶带、彩条、卡片和礼品包装纸对包装盒进行装饰。商店使用第三层包装(带泡沫聚苯乙烯球的盒子)发送玩具。玩具有三层装饰,每层装饰的功能都不同,各层装饰相互独立。购买玩具时您可以不要礼品包装,可以在商店挑选它时不要外包装盒,甚至不带盒子就买下它(有特殊折扣!)。玩具可以有任何装饰组合,但这些装饰都不会改变玩具的基本功能。

既然您已了解 Decorator 模式,我将说明如何在 C# 中实现它。

首先,创建一个接口 IRepository<T>:

public interface IRepository<T>
{
  void Add(T entity);
  void Delete(T entity);
  void Update(T entity);
  IEnumerable<T> GetAll();
  T GetById(int id);
}

通过 Repository<T> 类实现它,如图 1 所示。

图 1 Repository<T> 类

public class Repository<T> : IRepository<T>
{
  public void Add(T entity)
  {
    Console.WriteLine("Adding {0}", entity);
  }
  public void Delete(T entity)
  {
    Console.WriteLine("Deleting {0}", entity);
  }
  public void Update(T entity)
  {
    Console.WriteLine("Updating {0}", entity);
  }
  public IEnumerable<T> GetAll()
  {
    Console.WriteLine("Getting entities");
    return null;
  }
  public T GetById(int id)
  {
    Console.WriteLine("Getting entity {0}", id);
    return default(T);
  }
}

使用 Repository<T> 类添加、更新、删除和检索 Customer 类的元素:

public class Customer
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Address { get; set; }
}

程序可能如图 2 所示。

图 2 无日志记录的主程序

static void Main(string[] args)
{
  Console.WriteLine("***\r\n Begin program - no logging\r\n");
  IRepository<Customer> customerRepository =
    new Repository<Customer>();
  var customer = new Customer
  {
    Id = 1,
    Name = "Customer 1",
    Address = "Address 1"
  };
  customerRepository.Add(customer);
  customerRepository.Update(customer);
  customerRepository.Delete(customer);
  Console.WriteLine("\r\nEnd program - no logging\r\n***");
  Console.ReadLine();
}

运行这段代码时,会显示如图 3 所示的内容。

Output of the Program with No Logging
图 3 无日志记录的程序的输出

假设上级要求您向这个类添加日志记录。您可以创建一个新类对 IRepository<T> 进行修饰。它接收这个类,生成并实现同样的接口,如图 4 所示。

图 4 Logger Repository

public class LoggerRepository<T> : IRepository<T>
{
  private readonly IRepository<T> _decorated;
  public LoggerRepository(IRepository<T> decorated)
  {
    _decorated = decorated;
  }
  private void Log(string msg, object arg = null)
  {
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine(msg, arg);
    Console.ResetColor();
  }
  public void Add(T entity)
  {
    Log("In decorator - Before Adding {0}", entity);
    _decorated.Add(entity);
    Log("In decorator - After Adding {0}", entity);
  }
  public void Delete(T entity)
  {
    Log("In decorator - Before Deleting {0}", entity);
    _decorated.Delete(entity);
    Log("In decorator - After Deleting {0}", entity);
  }
  public void Update(T entity)
  {
    Log("In decorator - Before Updating {0}", entity);
    _decorated.Update(entity);
    Log("In decorator - After Updating {0}", entity);
  }
  public IEnumerable<T> GetAll()
  {
    Log("In decorator - Before Getting Entities");
    var result = _decorated.GetAll();
    Log("In decorator - After Getting Entities");
    return result;
  }
  public T GetById(int id)
  {
    Log("In decorator - Before Getting Entity {0}", id);
    var result = _decorated.GetById(id);
    Log("In decorator - After Getting Entity {0}", id);
    return result;
  }
}

这个新类对已修饰类的方法进行包装,添加日志记录功能。要调用该日志记录类,必须对代码进行略微更改,如图 5 所示。

图 5 使用 Logger Repository 的主程序

static void Main(string[] args)
{
  Console.WriteLine("***\r\n Begin program - logging with decorator\r\n");
  // IRepository<Customer> customerRepository =
  //   new Repository<Customer>();
  IRepository<Customer> customerRepository =
    new LoggerRepository<Customer>(new Repository<Customer>());
  var customer = new Customer
  {
    Id = 1,
    Name = "Customer 1",
    Address = "Address 1"
  };
  customerRepository.Add(customer);
  customerRepository.Update(customer);
  customerRepository.Delete(customer);
  Console.WriteLine("\r\nEnd program - logging with decorator\r\n***");
  Console.ReadLine();
}

您只需创建新类,传递旧类的实例作为其构造函数的参数。在执行程序时,可以看到它有日志记录,如图 6 所示。

Execution of the Logging Program with a Decorator
图 6 使用 Decorator 执行日志记录程序

您可能会认为:“想法是不错,但需要大量工作:我必须实现所有类并将方面添加到所有方法。这很难维护。有没有其他方法可以实现呢?”通过 .NET Framework,您可以使用反射来获取所有方法并加以执行。基类库 (BCL) 甚至有可用来执行该实现的 RealProxy 类 (bit.ly/18MfxWo)。

使用 RealProxy 创建动态代理

RealProxy 类提供基本代理功能。它是一个抽象类,必须通过重写其 Invoke 方法并添加新功能来继承。该类在命名空间 System.Runtime.Remoting.Proxies 中。要创建动态代理,可使用如图 7 所示的代码。

图 7 动态代理类

class DynamicProxy<T> : RealProxy
{
  private readonly T _decorated;
  public DynamicProxy(T decorated)
    : base(typeof(T))
  {
    _decorated = decorated;
  }
  private void Log(string msg, object arg = null)
  {
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine(msg, arg);
    Console.ResetColor();
  }
  public override IMessage Invoke(IMessage msg)
  {
    var methodCall = msg as IMethodCallMessage;
    var methodInfo = methodCall.MethodBase as MethodInfo;
    Log("In Dynamic Proxy - Before executing '{0}'",
      methodCall.MethodName);
    try
    {
      var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
      Log("In Dynamic Proxy - After executing '{0}' ",
        methodCall.MethodName);
      return new ReturnMessage(result, null, 0,
        methodCall.LogicalCallContext, methodCall);
    }
    catch (Exception e)
    {
     Log(string.Format(
       "In Dynamic Proxy- Exception {0} executing '{1}'", e),
       methodCall.MethodName);
     return new ReturnMessage(e, methodCall);
    }
  }
}

在该类的构造函数中,必须调用基类的构造函数,传递要修饰的类的类型。然后,必须重写将接收 IMessage 参数的 Invoke 方法。它包含一个字典,字典中是为该方法传递的所有参数。IMessage 参数会类型转换为 IMethodCallMessage,这样,就可以提取参数 MethodBase(具有 MethodInfo 类型)。

接下来的步骤是在调用该方法前添加所需的方面,使用 methodInfo.Invoke 调用原始方法,调用之后添加该方面。

您不能直接调用代理,因为 DynamicProxy<T> 不是 IRepository<Customer>。这意味着您不能这样调用它:

IRepository<Customer> customerRepository =
  new DynamicProxy<IRepository<Customer>>(
  new Repository<Customer>());

要使用经过修饰的存储库,您必须使用 GetTransparentProxy 方法,此方法将返回 IRepository<Customer> 的实例。所调用的此实例的每个方法都将经历该代理的 Invoke 方法。为了方便实现此过程,您可以创建一个 Factory 类来创建代理并返回存储库的实例:

public class RepositoryFactory
{
  public static IRepository<T> Create<T>()
  {
    var repository = new Repository<T>();
    var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
    return dynamicProxy.GetTransparentProxy() as IRepository<T>;
  }
}

这样,主程序如图 8 所示。

图 8 带动态代理的主程序

static void Main(string[] args)
{
  Console.WriteLine("***\r\n Begin program - logging with dynamic proxy\r\n");
  // IRepository<Customer> customerRepository =
  //   new Repository<Customer>();
  // IRepository<Customer> customerRepository =
  //   new LoggerRepository<Customer>(new Repository<Customer>());
  IRepository<Customer> customerRepository =
    RepositoryFactory.Create<Customer>();
  var customer = new Customer
  {
    Id = 1,
    Name = "Customer 1",
    Address = "Address 1"
   ;
  customerRepository.Add(customer);
  customerRepository.Update(customer);
  customerRepository.Delete(customer);
  Console.WriteLine("\r\nEnd program - logging with dynamic proxy\r\n***");
  Console.ReadLine();
}

在执行此程序时,结果和前面类似,如图 9 所示。

Program Execution with Dynamic Proxy
图 9 使用动态代理的程序执行

可以看到,您已创建一个动态代理,可将方面添加到代码,无需重复该操作。如果要添加一个新方面,只需创建一个新类,从 RealProxy 继承,用它来修饰第一个代理。

如果上级又要求您向代码中添加授权,以便只有管理员才能访问存储库,则可以创建一个新代理,如图 10 所示。

图 10 身份验证代理

class AuthenticationProxy<T> : RealProxy
{
  private readonly T _decorated;
  public AuthenticationProxy(T decorated)
    : base(typeof(T))
  {
    _decorated = decorated;
  }
  private void Log(string msg, object arg = null)
  {
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine(msg, arg);
    Console.ResetColor();
  }
  public override IMessage Invoke(IMessage msg)
  {
    var methodCall = msg as IMethodCallMessage;
    var methodInfo = methodCall.MethodBase as MethodInfo;
    if (Thread.CurrentPrincipal.IsInRole("ADMIN"))
    {
      try
      {
        Log("User authenticated - You can execute '{0}' ",
          methodCall.MethodName);
        var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
        return new ReturnMessage(result, null, 0,
          methodCall.LogicalCallContext, methodCall);
      }
      catch (Exception e)
      {
        Log(string.Format(
          "User authenticated - Exception {0} executing '{1}'", e),
          methodCall.MethodName);
        return new ReturnMessage(e, methodCall);
      }
    }
    Log("User not authenticated - You can't execute '{0}' ",
      methodCall.MethodName);
    return new ReturnMessage(null, null, 0,
      methodCall.LogicalCallContext, methodCall);
  }
}

必须更改存储库工厂才能调用两个代理,如图 11 所示。

图 11 由两个代理修饰的存储库工厂

public class RepositoryFactory
{
  public static IRepository<T> Create<T>()
  {
    var repository = new Repository<T>();
    var decoratedRepository =
      (IRepository<T>)new DynamicProxy<IRepository<T>>(
      repository).GetTransparentProxy();
    // Create a dynamic proxy for the class already decorated
    decoratedRepository =
      (IRepository<T>)new AuthenticationProxy<IRepository<T>>(
      decoratedRepository).GetTransparentProxy();
    return decoratedRepository;
  }
}

如果将主程序更改为如图 12 所示,然后运行,则输出如图 13 所示。

图 12 使用两个用户调用存储库的主程序

static void Main(string[] args)
{
  Console.WriteLine(
    "***\r\n Begin program - logging and authentication\r\n");
  Console.WriteLine("\r\nRunning as admin");
  Thread.CurrentPrincipal =
    new GenericPrincipal(new GenericIdentity("Administrator"),
    new[] { "ADMIN" });
  IRepository<Customer> customerRepository =
    RepositoryFactory.Create<Customer>();
  var customer = new Customer
  {
    Id = 1,
    Name = "Customer 1",
    Address = "Address 1"
  };
  customerRepository.Add(customer);
  customerRepository.Update(customer);
  customerRepository.Delete(customer);
  Console.WriteLine("\r\nRunning as user");
  Thread.CurrentPrincipal =
    new GenericPrincipal(new GenericIdentity("NormalUser"),
    new string[] { });
  customerRepository.Add(customer);
  customerRepository.Update(customer);
  customerRepository.Delete(customer);
  Console.WriteLine(
    "\r\nEnd program - logging and authentication\r\n***");
  Console.ReadLine();
}

Output of the Program Using Two Proxies
图 13 使用两个代理的程序的输出

程序执行两次存储库方法。第一次,它以管理员用户身份运行,并调用这些方法。第二次,它以普通用户身份运行,并跳过这些方法。

这要容易很多,对吧?请注意,工厂返回 IRepository<T> 的实例,因此,程序不知道它是否正在使用经过修饰的版本。这符合里氏替换原则,即如果 S 是 T 的子类型,则类型 T 的对象可以替换为类型 S 的对象。这种情况下,通过使用 IRepository<Customer> 接口,可以使用可实现此接口的任何类而不必更改程序。

筛选功能

到现在为止,函数中没有筛选;该方面应用于所调用的每个类方法。这通常不是所希望的行为。例如,您可能不需要记录检索方法(GetAll 和 GetById)。要实现这一点,一种方式是按名称对该方面进行筛选,如图 14 所示。

图 14 针对方面筛选方法

public override IMessage Invoke(IMessage msg)
{
  var methodCall = msg as IMethodCallMessage;
  var methodInfo = methodCall.MethodBase as MethodInfo;
  if (!methodInfo.Name.StartsWith("Get"))
    Log("In Dynamic Proxy - Before executing '{0}'",
      methodCall.MethodName);
  try
  {
    var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
    if (!methodInfo.Name.StartsWith("Get"))
      Log("In Dynamic Proxy - After executing '{0}' ",
        methodCall.MethodName);
      return new ReturnMessage(result, null, 0,
       methodCall.LogicalCallContext, methodCall);
  }
  catch (Exception e)
  {
    if (!methodInfo.Name.StartsWith("Get"))
      Log(string.Format(
        "In Dynamic Proxy- Exception {0} executing '{1}'", e),
        methodCall.MethodName);
      return new ReturnMessage(e, methodCall);
  }
}

程序检查方法是否以“Get”开头。如果是,则程序不应用该方面。这样是可行的,但是要重复三次筛选代码。此外,筛选器在代理内,这样,每次要更改代理都需要更改该类。通过创建 IsValidMethod 谓词可以对此进行改进:

private static bool IsValidMethod(MethodInfo methodInfo)
{
  return !methodInfo.Name.StartsWith("Get");
}

现在,您只需在一处更改,但是每次更改筛选器时都需要更改该类。一个解决方法是将筛选器公开为类属性,这样可以将创建筛选器的责任分配给调用方。您可以创建类型 Predicate<MethodInfo> 的 Filter 属性,用它来筛选数据,如图 15 所示。

图 15 筛选代理

class DynamicProxy<T> : RealProxy
{
  private readonly T _decorated;
  private Predicate<MethodInfo> _filter;
  public DynamicProxy(T decorated)
    : base(typeof(T))
  {
    _decorated = decorated;
    _filter = m => true;
  }
  public Predicate<MethodInfo> Filter
  {
    get { return _filter; }
    set
    {
      if (value == null)
        _filter = m => true;
      else
        _filter = value;
    }
  }
  private void Log(string msg, object arg = null)
  {
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine(msg, arg);
    Console.ResetColor();
  }
  public override IMessage Invoke(IMessage msg)
  {
    var methodCall = msg as IMethodCallMessage;
    var methodInfo = methodCall.MethodBase as MethodInfo;
    if (_filter(methodInfo))
      Log("In Dynamic Proxy - Before executing '{0}'",
        methodCall.MethodName);
    try
    {
      var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
      if (_filter(methodInfo))
        Log("In Dynamic Proxy - After executing '{0}' ",
          methodCall.MethodName);
        return new ReturnMessage(result, null, 0,
          methodCall.LogicalCallContext, methodCall);
    }
    catch (Exception e)
    {
      if (_filter(methodInfo))
        Log(string.Format(
          "In Dynamic Proxy- Exception {0} executing '{1}'", e),
          methodCall.MethodName);
      return new ReturnMessage(e, methodCall);
    }
  }
}

Filter 属性可通过 Filter = m => true 初始化。这意味着没有处于活动状态的筛选器。在为 Filter 属性赋值时,程序会验证值是否为 null,如果是,则程序重置筛选器。在 Invoke 方法执行中,程序会检查筛选结果,如果结果为 true,则程序应用该方面。现在,工厂类中的代理创建如下所示:

public class RepositoryFactory
{
  public static IRepository<T> Create<T>()
  {
    var repository = new Repository<T>();
    var dynamicProxy = new DynamicProxy<IRepository<T>>(repository)
    {
      Filter = m => !m.Name.StartsWith("Get")
      };
      return dynamicProxy.GetTransparentProxy() as IRepository<T>;
    }  
  }
}

创建筛选器的责任已转移给工厂。运行程序时,结果如图 16 所示。

Output with a Filtered Proxy
图 16 使用筛选代理时的输出

请注意,在图 16 中,最后两个方法(GetAll 和 GetById,由“Getting entities”和“Getting entity 1”表示)没有日志记录。通过将这些方面公开为事件,可以进一步增强该类。这样,不必在每次需要更改该方面时都更改该类。如图 17 所示。

图 17 灵活代理

class DynamicProxy<T> : RealProxy
{
  private readonly T _decorated;
  private Predicate<MethodInfo> _filter;
  public event EventHandler<IMethodCallMessage> BeforeExecute;
  public event EventHandler<IMethodCallMessage> AfterExecute;
  public event EventHandler<IMethodCallMessage> ErrorExecuting;
  public DynamicProxy(T decorated)
    : base(typeof(T))
  {
    _decorated = decorated;
    Filter = m => true;
  }
  public Predicate<MethodInfo> Filter
  {
    get { return _filter; }
    set
    {
      if (value == null)
        _filter = m => true;
      else
        _filter = value;
    }
  }
  private void OnBeforeExecute(IMethodCallMessage methodCall)
  {
    if (BeforeExecute != null)
    {
      var methodInfo = methodCall.MethodBase as MethodInfo;
      if (_filter(methodInfo))
        BeforeExecute(this, methodCall);
    }
  }
  private void OnAfterExecute(IMethodCallMessage methodCall)
  {
    if (AfterExecute != null)
    {
      var methodInfo = methodCall.MethodBase as MethodInfo;
      if (_filter(methodInfo))
        AfterExecute(this, methodCall);
    }
  }
  private void OnErrorExecuting(IMethodCallMessage methodCall)
  {
    if (ErrorExecuting != null)
    {
      var methodInfo = methodCall.MethodBase as MethodInfo;
      if (_filter(methodInfo))
        ErrorExecuting(this, methodCall);
    }
  }
  public override IMessage Invoke(IMessage msg)
  {
    var methodCall = msg as IMethodCallMessage;
    var methodInfo = methodCall.MethodBase as MethodInfo;
    OnBeforeExecute(methodCall);
    try
    {
      var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
      OnAfterExecute(methodCall);
      return new ReturnMessage(
        result, null, 0, methodCall.LogicalCallContext, methodCall);
    }
    catch (Exception e)
    {
      OnErrorExecuting(methodCall);
      return new ReturnMessage(e, methodCall);
    }
  }
}

图 17 中,BeforeExecute、AfterExecute 和 ErrorExecuting 这三个事件由方法 OnBeforeExecute、OnAfterExecute 和 OnErrorExecuting 调用。这些方法验证是否定义了事件处理程序,如果已定义,则检查所调用的方法是否通过筛选器。如果是,则调用应用该方面的事件处理程序。该工厂类现在如图 18 所示。

图 18 用于设置方面事件和筛选器的存储库工厂

public class RepositoryFactory
{
  private static void Log(string msg, object arg = null)
  {
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine(msg, arg);
    Console.ResetColor();
  }
  public static IRepository<T> Create<T>()
  {
    var repository = new Repository<T>();
    var dynamicProxy = new DynamicProxy<IRepository<T>>(repository);
    dynamicProxy.BeforeExecute += (s, e) => Log(
      "Before executing '{0}'", e.MethodName);
    dynamicProxy.AfterExecute += (s, e) => Log(
      "After executing '{0}'", e.MethodName);
    dynamicProxy.ErrorExecuting += (s, e) => Log(
      "Error executing '{0}'", e.MethodName);
    dynamicProxy.Filter = m => !m.Name.StartsWith("Get");
    return dynamicProxy.GetTransparentProxy() as IRepository<T>;
  }
}

现在有了一个灵活代理类,您可以选择要在执行之前、执行之后或在发生错误时仅针对所选方法应用的方面。这样,可以方便地在代码中不重复地应用许多方面。

并非替换

通过 AOP,您可以集中向应用程序的所有层添加代码,无需重复代码。我说明了如何基于 Decorator 设计模式来创建将方面应用到使用事件的类的泛型动态代理,并创建用于筛选所需函数的谓词。

可以看到,RealProxy 类是一个灵活类,可用来全面控制代码而无需外部依赖关系。但是请注意,RealProxy 并不是用来替换其他 AOP 工具,如 PostSharp。PostSharp 使用完全不同的方法。它在编译后步骤中添加中间语言 (IL) 代码,不使用反射,因此它与 RealProxy 相比具有更好的性能。相比 PostSharp,要使用 RealProxy 实现某个方面,您还必须完成更多工作。通过 PostSharp,您只需要创建方面类,向要添加该方面的类(或方法)添加属性,就这么简单。

另一方面,通过 RealProxy,无需任何外部依赖关系就可以全面控制源代码,可根据自己的需要进行扩展和自定义。例如,如果只需要对具有 Log 属性的方法应用某个方面,则可以执行如下操作:

public override IMessage Invoke(IMessage msg)
{
  var methodCall = msg as IMethodCallMessage;
  var methodInfo = methodCall.MethodBase as MethodInfo;
  if (!methodInfo.CustomAttributes
    .Any(a => a.AttributeType == typeof (LogAttribute)))
  {
    var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
    return new ReturnMessage(result, null, 0,
      methodCall.LogicalCallContext, methodCall);
  }
    ...

此外,RealProxy 所使用的方法(拦截代码并允许程序将其替换)功能十分强大。例如,如果需要创建一个模拟框架,以创建测试用的泛型模拟和存根,则可以使用 RealProxy 类来拦截所有调用,并替换为您自己的行为,不过,这将在另一篇文章中介绍!

Bruno Sonnino 是 Microsoft 最有价值专家 (MVP),居住在巴西。他是开发人员、顾问和作者,著有五部有关 Delphi 的书籍(由 Pearson Education Brazil 出版,葡萄牙语),并为巴西和美国的杂志和网站撰写了许多文章。

衷心感谢以下 Microsoft Research 技术专家对本文的审阅:James McCaffrey、Carlos Suarez 和 Johan Verwey
James McCaffrey 供职于华盛顿州雷德蒙德市的 Microsoft Research。他参与过多个 Microsoft 产品的工作,包括 Internet Explorer 和 Bing。可通过 jammc@microsoft.com 与他联系。

Carlos Garcia Jurado Suarez 是 Microsoft Research 的研究软件工程师,他曾在高级开发团队 (Advanced Development Team) 工作,最近在机器学习小组 (Machine Learning Group) 工作。早些时候,他是 Visual Studio 开发人员,从事建模工具(如 Class Designer)的开发。