65.9K
CodeProject 正在变化。 阅读更多。
Home

Castle 的 .NET DynamicProxy

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (17投票s)

2004年12月14日

3分钟阅读

viewsIcon

199015

downloadIcon

2

解释如何使用 DynamicProxy 拦截对象实例。

引言

CLR 提供的代理功能非常棒。 ProxyAttribute、消息接收器等都是非常棒的主意,可以轻松扩展平台。

然而,也有一些缺点。 例如:要使用代理功能,您的类必须继承 MarshalByRefContextBoundObject(它带有另一个语义),这样做会弄乱您的对象模型层次结构。 这可能非常具有侵入性。 尽管如此,如果您控制对象模型,这仍然是一个选择。 但是当事情来自外部时,没有简单的方法可以实现它。

本文解释了如何使用 DynamicProxy(可在 Castle Project 上找到)以快速、干净的方式为您的类创建拦截器,并且具有良好的性能,因为 DynamicProxy 不使用反射来调用对象实例上的方法。

如何使用

DynamicProxy 能够代理接口和具体类。 您始终需要提供要代理的 Type 和拦截器实例。 拦截器将在代理上调用每个方法时被调用,因此您可以继续您的拦截器逻辑(事务、日志记录等),然后继续或不继续调用。 如果您选择继续,则必须在提供的调用对象上调用 Proceed 方法。

public class MyInterceptor : IInterceptor
{
    public object Intercept(IInvocation invocation, params object[] args)
    {
        DoSomeWorkBefore(invocation, args);

        object retValue = invocation.Proceed( args );

        DoSomeWorkAfter(invocation, retValue, args);

        return retValue;
    }
}

IInvocation 接口为您提供了很多有用的信息

public interface IInvocation
{
    object Proxy { get; }

    object InvocationTarget { get; set; }

    MethodInfo Method { get; }

    object Proceed( params object[] args );
}

对于接口,您还必须指定将成为调用的目标的实例对象。 复杂吗? 真的没有。 考虑以下示例

public interface IMyInterface
{
    int Calc(int x, int y);
    int Calc(int x, int y, int z, Single k);
}

public class MyInterfaceImpl : IMyInterface
{
    public virtual int Calc(int x, int y)
    {
        return x + y;
    }

    public virtual int Calc(int x, int y, int z, Single k)
    {
        return x + y + z + (int)k;
    }
}

ProxyGenerator generator = new ProxyGenerator();

IMyInterface proxy = (IMyInterface) generator.CreateProxy( 
    typeof(IMyInterface), new StandardInterceptor(), new MyInterfaceImpl() );

这是必需的,因为 DynamicProxy 需要一个默认的目标用于调用 - 我们稍后会解释原因。

现在,如果您想代理具体类,也有两个要求:该类不能被 sealed 并且只能拦截 virtual 方法。 原因是 DynamicProxy 将创建一个类的子类,覆盖所有方法,以便它可以将调用分派给拦截器。 请参阅以下示例

ProxyGenerator generator = new ProxyGenerator();

Hashtable proxy = (Hashtable) generator.CreateClassProxy( 
    typeof(Hashtable), new HashtableInterceptor() );

object value = proxy["key"]; // == "default"

public class HashtableInterceptor : StandardInterceptor
{
    public override object Intercept(IInvocation invocation, params object[] args)
    {
        if (invocation.Method.Name.Equals("get_Item"))
        {
            object item = base.Intercept(invocation, args);
            return (item == null) ? "default" : item;
        }
        return base.Intercept(invocation, args);
    }
}

如果您要代理的类没有公开默认构造函数,也没关系。 您只需要向 CreateClassProxy 提供参数即可。

Mixins

Mixin 是 C++ 世界中众所周知的继承方式。 简而言之,mixin 式的继承是能够将一个类与其他(或多个)公开单个特定功能的类混合在一起。 DynamicProxy 允许您将一个类与其他类混合,从而产生一个混合的代理实例。 如果 mixin 类公开接口,它们将由代理实例自动公开。

public interface ISimpleMixin
{
    int DoSomething();
}

public class SimpleMixin : ISimpleMixin
{
    public int DoSomething()
    {
        return 1;
    }
}

ProxyGenerator generator = new ProxyGenerator();
GeneratorContext context = new GeneratorContext();

SimpleMixin mixin_instance = new SimpleMixin();

context.AddMixinInstance( mixin_instance );

SimpleClass proxy = (SimpleClass) generator.CreateCustomClassProxy( 
    typeof(SimpleClass), interceptor, context );

ISimpleMixin mixin = (ISimpleMixin) proxy;

像 Ruby 这样的动态语言是 mixin 的天堂,因为混合模块中的所有内容都由目标类公开,但这属于另一场讨论。

Aspect# 将 mixin 提升了一个级别,因为它实现了一个协议,允许 mixin 实例引用代理实例。 但是,DynamicProxy 将这种决定留给开发人员。

幕后

DynamicProxy 动态生成一个类似于以下(伪)代码的类

public class ProxyGenerated : YourClass
{
  // Exposes all constructors
  
  // Overrides all methods
  
  public override int DoSomething(int x)
  {
    MethodInfo m = ldtoken currentMethod;
    IInvocation invocation = ObtainInvocationFor( deletegateForDoSomething, 
                             callableForDoSomething, m);
    return (int) _interceptor.Intercept( invocation, x );
  }

  private int BaseCallDoSomething(int x)
  {
    return base.DoSomething(x);
  }
}

正如您所看到的,DynamicProxy 依靠委托来实现良好的性能。 唯一的性能瓶颈发生在装箱和拆箱值类型时。 基本上,每个委托都会有一个指向另一个方法的指针,该方法只调用基类方法。 当您在 IInvocation 实例上使用 Proceed 时,就会发生这种情况。

结论

对于程序集的静态编织,请考虑简洁精干的 RAIL 项目。 如果您的需求是动态的,请考虑 DynamicProxy。 我还需要感谢通过 Aspect# 和 Castle-devel 邮件列表收到的所有反馈和补丁。 DynamicProxy 的用户给了我一次又一次重构和调整它的动力。

有关 DynamicProxy 的更新信息,请查看 其站点

历史

  • 2004 年 12 月 13 日 - 初始版本。
© . All rights reserved.