Castle 的 .NET DynamicProxy






4.56/5 (17投票s)
2004年12月14日
3分钟阅读

199015

2
解释如何使用 DynamicProxy 拦截对象实例。
引言
CLR 提供的代理功能非常棒。 ProxyAttribute
、消息接收器等都是非常棒的主意,可以轻松扩展平台。
然而,也有一些缺点。 例如:要使用代理功能,您的类必须继承 MarshalByRef
或 ContextBoundObject
(它带有另一个语义),这样做会弄乱您的对象模型层次结构。 这可能非常具有侵入性。 尽管如此,如果您控制对象模型,这仍然是一个选择。 但是当事情来自外部时,没有简单的方法可以实现它。
本文解释了如何使用 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 日 - 初始版本。