How to use Castle.DynamicProxy-2.1, part 1
PⅠ, Introduction
最近对AOP比较感兴趣, 下载了一个.net下的AOP框架实现(Castle.DynamicProxy), 关于怎么使用, 网上大部分都是有关1.x版本的, 我们当然倾向于使用最新的2.1版本. 链接[1]是Castle官网推荐的使用说明, 本文主要是记录其核心和我的一些体会.
DynamicProxy确实非常强大.
PⅡ, On The Way
2.1, Structure & API revelation
---
2.1.1
Name: IInterceptor
When to use: 封装实现一个具体拦截行为类
Code:
2{
3 void Intercept(IInvocation invocation);
4}
所有的'拦截器'都必须实现IInterceptor, 因为在代码生成派生的代理类时, 其构造函数参数为IInterceptor, 依靠构造器动态'注入'具体拦截行为.
2.1.2
Name: IInvocation
When: 回访调用方法属性
Code:
2{
3 object Proxy { get; }
4
5 object InvocationTarget { get; }
6
7 //Others
8
9 void Proceed();
10}
IInvocation可以看做指向原始方法的delegate的封装, 在代码动态生成期间, 原始方法会挂钩一个IInvocation, IInvocation中的Proxy指向生成的代理类对象, Proceed()委托执行原始方法.
2.1.3
Name: IProxyGenerationHook
When: 定制代码生成期间的Method/Interceptor匹配对(ShouldInterceptMethod()), 检查通知不能拦截的方法(NonVirutalMemberNotification()), 以及在完成匹配后的扫尾工作, 比如释放资源(MethodsInspected()).
How:
var options = new ProxyGenerationOptions(new XXXProxyGenerationHook());
Code:
public interface IProxyGenerationHook
{
bool ShouldInterceptMethod(Type type, MethodInfo methodInfo);
void NonVirtualMemberNotification(Type type, MemberInfo memberInfo);
void MethodsInspected();
}
作为CreateProxyXXX系列方法的option参数传入, 其实现类所有的方法调用都是在代理类对象生成期间完成(对比下面的对象执行期匹配).
2.1.4
Name: IInterceptorSelector
When: 定制对象执行期间的Method/Interceptor匹配对
How:
IInterceptorSelector selector = new FreezableInterceptorSelector();
var options = new ProxyGenerationOptions(new XXXProxyGenerationHook()) {Selector = selector};
Code:
2{
3 IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors);
4}
作为对象执行期间的匹配原则, 只有通过IProxyGenerationHook规则(代码类生成期间)的方法才能进一步进行匹配筛选.
2.1.5
Name: IProxyTargetAccessor
When: 从伪对象取得真正的代理对象(DynProxyGetTarget()), 取得所有的拦截器(GetInterceptors())
How:
var object = ProxyGenerator.CreateProxyXXX(...);
IInterceptor[] interceptorList = (object as IProxyTargetAccessor).GetInterceptors();
Code:
2{
3 object DynProxyGetTarget();
4
5 IInterceptor[] GetInterceptors();
6}
一看就知道非常有用.
2.1.6
Name: IChangeProxyTarget
When: 使用CreateInterfaceProxyWithTargetInterface创建proxy, 并需要改变代理的实际对象时.
How:
var changeProxyTarget = invocation as IChangeProxyTarget;
changeProxyTarget.ChangeInvocationTarget(otherImp);
Code:
2{
3 void ChangeInvocationTarget(object target);
4}
因为IInvocation接口包含部分属性(关键是其引用了具体的Proxy). 注意, 转换后的具体对象必须也实现了代理接口.
2.1.7 Contrast with four types of proxy creation
在方法重载中, 少参数方法多半是委托调用多参数方法实现, 并设置额外参数为默认. 所以我们只考虑完全参数的方法实现.
Code:
1. public object CreateClassProxy(Type classToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, object[]constructorArguments, params IInterceptor[] interceptors);
2. public object CreateInterfaceProxyWithoutTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, ProxyGenerationOptions options, params IInterceptor[] interceptors);
3. public object CreateInterfaceProxyWithTarget(Type interfaceToProxy, Type[] additionalInterfacesToProxy, object target, ProxyGenerationOptions options, params IInterceptor[] interceptors);
4. public object CreateInterfaceProxyWithTargetInterface(Type interfaceToProxy, Type[] additionalInterfacesToProxy, object target, ProxyGenerationOptions options, params IInterceptor[] interceptors);
通用参数:
Type classToProxy or interfaceToProxy, 代理对象类型.
ProxyGenerationOptions options, 这里定义methods/interceptors匹配规则, 混合多接口(Minix) 以及其他选项.
object[] constructorArguments, 代理对象类型的构造函数参数.
IInterceptor[] interceptors, 一组拦截器对象.
特殊参数:
@3@4, object target, 接口(interfaceToProxy)的一个具体实现对象.
其他:
关于CreateInterfaceProxyWithoutTarget的调用, 因为接口是不可能创建对象的, 代码会自动生成一个实现类, 但该类却不会产生任何逻辑(这是理所当然的), 所以这种情况下我们不能在拦截器的Intercept方法中调用invocation.Process().
CreateClassProxy()与CreateInterfaceProxyWithTarget类似. 由于Proxy对象的生成本质上是继承(实现)classToProxy(interfaceToProxy)类型, 并override其中的virtual method, 达到改变行为的目的('修改代码的艺术'中的子类化并重写手法). 所以当欲代理对象类型不可继承时(sealed), 我们就不能使用CreateClassProxy()了. 这种情况下, 我们可以提取代理方法接口, 并使用CreateInterfaceProxyWithTarget()方法创建proxy. 我猜想使用CreateInterfaceproxyWithTarget(Type interfaceToProxy, ..., object target, ...)创建的proxy是实现了interfaceToProxy类型的对象, 并内部包含target成员, 各方法委托其实现.
使用CreateInterfaceProxyWithTargetInterface创建proxy时, 我们可以在任何可以访问IInvocation对象时转换具体的代理对象(常用做法是在拦截器中保存真实的对象引用, 并在其拦截行为中选择进行替换工作). 需要注意的是, 如果代理对象拥有多组拦截器, 在某一层拦截并替换后, 其后拦截器访问到的invocation对象引用的都是替换后的对象, 但是, 在一轮操作过后(某一个方法的所有拦截行为与其具体行为执行), 代理指向的实际对象会被重置, 即指向原始对象. 我们可以发现, 其实这是符合逻辑的(AOP). 在典型的OO设计中, 我们若进行了接口对象的替换操作, 因为不管之前还是之后的对象, 我们都会重写需要(需要仅对于基于基类的继承)的虚方法, 所以我们得到的对象都是一个个'自完全'的. 但在AOP中, 拦截的切入只能基于一种情况的约定, 可以看做一种契约形式, 如果真实的代理对象被替换后, 我们仍用原始的拦截器拦截, 那么首先就违背这个拦截器的使用约定. 如果此时需要维持逻辑上的正确, 我们就得配置一个与新对象适配的新拦截器来替换, 这等效于替换OO对象, 改写所有的虚方法.
---
2.2 将介绍应用demo, 2.3 是源代码分析.
-----------------------------------------------
Ref:
[1], Castle dynamic proxy tutorial, http://kozmic.pl/archive/2009/04/27/castle-dynamic-proxy-tutorial.aspx