Castle DynamicProxy动态生成透明代理类型,实体不需要继承MarshalByRef、ContextBoundObject便可以实现代理类
基于透明代理的功能,可以实现对方法调用的拦截处理,例如NHibernate用它实现延迟加载
DP的使用非常简单,内部没有使用反射,而是采用Emit、委托等方式生成代理类型,调用真实类的方法,性能方面也没有太多损失
基本示例
引用的命名空间:
using Castle.Core.Interceptor; using Castle.DynamicProxy;被代理的实体类:
public class SimpleSamepleEntity { public virtual string Name { get; set; } public virtual int Age { get; set; } public override string ToString() { return string.Format("{{ Name: \"{0}\", Age: {1} }}", this.Name, this.Age); } }定义一个拦截器,调用代理类的方法时使用拦截器进行拦截处理:
public class CallingLogInterceptor : IInterceptor { private int _indent = 0; private void PreProceed(IInvocation invocation) { if (this._indent > 0) Console.Write(" ".PadRight(this._indent * 4, ' ')); this._indent++; Console.Write("Intercepting: " + invocation.Method.Name + "("); if (invocation.Arguments != null && invocation.Arguments.Length > 0) for (int i = 0; i < invocation.Arguments.Length; i++) { if (i != 0) Console.Write(", "); Console.Write(invocation.Arguments[i] == null ? "null" : invocation.Arguments[i].GetType() == typeof(string) ? "\"" + invocation.Arguments[i].ToString() + "\"" : invocation.Arguments[i].ToString()); } Console.WriteLine(")"); } private void PostProceed(IInvocation invocation) { this._indent--; } public void Intercept(IInvocation invocation) { this.PreProceed(invocation); invocation.Proceed(); this.PostProceed(invocation); } }测试代码:
ProxyGenerator generator = new ProxyGenerator(); CallingLogInterceptor interceptor = new CallingLogInterceptor(); SimpleSamepleEntity entity = generator.CreateClassProxy运行结果:(interceptor); entity.Name = "Richie"; entity.Age = 50; Console.WriteLine("The entity is: " + entity); Console.WriteLine("Type of the entity: " + entity.GetType().FullName); Console.ReadKey();
上面示例使用CreateClassProxy方法创建了一个代理对象entity,从图中输出的entity类型可以看到他的类型为Castle.Proxies.SimpleSampleEntity
当调用代理对象的任何方法时,将使用CallingLogInterceptor这个拦截器进行处理,在CallingLogInterceptor中输出了调用日志信息
在拦截器的接口方法Intercept中,可以修改入参,决定是否调用真实方法,修改返回结果等
可以拦截的方法:
1. 对于Class Proxy类型的代理,因为DP使用继承和override虚方法的方式实现代理,所以被拦截的方法必须为virtual类型,非virtual类型的方法无法拦截
2. 实体继承自Object、MarshalByRefObject等对象的方法,如果在实体类中没有override将无法拦截
DP会将动态创建的代理类型缓存起来
Interceptor Pipeline
创建代理对象时可以指定多个拦截器,例如再简单的实现一个拦截器:
public class SimpleLogInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { Console.WriteLine(">>" + invocation.Method.Name); invocation.Proceed(); } }测试代码如下修改即可:
ProxyGenerator generator = new ProxyGenerator(); SimpleSamepleEntity entity = generator.CreateClassProxy运行结果:( new SimpleLogInterceptor(), new CallingLogInterceptor()); entity.Name = "Richie"; entity.Age = 50; Console.WriteLine("The entity is: " + entity); Console.WriteLine("Type of the entity: " + entity.GetType().FullName); Console.ReadKey();
多个拦截器之间以pipeline方式处理调用顺序:
实际中并不一定所有方法都需要运用全部的拦截器,对方法调用有选择性的选择拦截器有2种方式,例如:
public class InterceptorSelector : IInterceptorSelector { public IInterceptor[] SelectInterceptors(Type type, MethodInfo method, IInterceptor[] interceptors) { if (method.Name.StartsWith("set_")) return interceptors; else return interceptors.Where(i => i is CallingLogInterceptor).ToArray测试代码改为:(); } } public class InterceptorFilter : IProxyGenerationHook { public bool ShouldInterceptMethod(Type type, MethodInfo memberInfo) { return memberInfo.IsSpecialName && (memberInfo.Name.StartsWith("set_") || memberInfo.Name.StartsWith("get_")); } public void NonVirtualMemberNotification(Type type, MemberInfo memberInfo) { } public void MethodsInspected() { } }
ProxyGenerator generator = new ProxyGenerator(); var options = new ProxyGenerationOptions(new InterceptorFilter()) { Selector = new InterceptorSelector() }; SimpleSamepleEntity entity = generator.CreateClassProxy运行结果:( options, new SimpleLogInterceptor(), new CallingLogInterceptor()); entity.Name = "Richie"; entity.Age = 50; Console.WriteLine("The entity is: " + entity); Console.WriteLine("Type of the entity: " + entity.GetType().FullName); Console.ReadKey();
IProxyGenerationHook接口决定整个方法是否运用拦截器,他是在动态构造代理类型的时候使用的;而IInterceptorSelector接口决定某个方法该运用哪些拦截器,他在每次调用被拦截的方法时执行
上面的示例只对setter和getter方法进行拦截,并且对getter方法只使用CallingLogInterceptor这个拦截器
Proxy Types
代理类型有class proxy、interface proxy with target、interface proxy without target、interface proxy with interface target几种
上面示例使用的是class proxy,使用CreateClassProxy方法创建,该方法的某些版本要求有默认构造器,不使用默认构造器时则必须传入构造参数。代理对象为class proxy类型时,被拦截的方法必须为virtual,non-virtual的方法无法实现拦截
interface proxy with target其实跟class proxy差不多,在创建代理对象时client指定接口,并且提供一个实现了该接口的对象作为真实对象,DP将创建这个接口的代理对象,对代理对象方法的调用经过拦截器处理之后,最终将调用真实对象相应的方法。与class proxy的不同之处在于,真实对象的方法不必是virtual类型也可以实现拦截
interface proxy without target比较特殊,创建代理时只需要指定一个接口就可以,DP自动根据接口构造一个实现的类,作为代理对象的类型,但这个代理类只能用于拦截目的,无法像class proxy一样在拦截器中调用真实对象的处理方法。比如在提供了多个拦截器时,最后一个拦截器的接口方法中不能调用invocation.Proceed()方法,否则会抛异常(因为真实对象根本不存在,只有一个假的代理对象)
interface proxy with interface target与interface proxy with target基本类似,但他提供了一个更改被代理对象(真实对象)的机会,示例如下:
public interface IStorageNode { bool IsDead { get; set; } void Save(string message); } public class StorageNode : IStorageNode { private string _name; public StorageNode(string name) { this._name = name; } public bool IsDead { get; set; } public void Save(string message) { Console.WriteLine(string.Format("\"{0}\" was saved to {1}", message, this._name)); } } public class DualNodeInterceptor : IInterceptor { private IStorageNode _slave; public DualNodeInterceptor(IStorageNode slave) { this._slave = slave; } public void Intercept(IInvocation invocation) { IStorageNode master = invocation.InvocationTarget as IStorageNode; if (master.IsDead) { IChangeProxyTarget cpt = invocation as IChangeProxyTarget; //将被代理对象master更换为slave cpt.ChangeProxyTarget(this._slave); //测试中恢复master的状态,以便看到随后的调用仍然使用master这一效果 master.IsDead = false; } invocation.Proceed(); } }测试代码:
ProxyGenerator generator = new ProxyGenerator(); IStorageNode node = generator.CreateInterfaceProxyWithTargetInterface运行结果:( new StorageNode("master") , new DualNodeInterceptor(new StorageNode("slave")) , new CallingLogInterceptor()); node.Save("my message"); //应该调用master对象 node.IsDead = true; node.Save("my message"); //应该调用slave对象 node.Save("my message"); //应该调用master对象 Console.ReadKey();
只有在interface proxy with interface target的情况下IInterceptor的接口参数IInvocation对象才实现了IChangeProxyTarget接口
IChangeProxyTarget的ChangeInvocationTarget方法将本次调用的被代理对象替换掉,而ChangeProxyTarget方法则永久的将被代理对象替换,但不包括本次调用
Mixins
要准确的将mixin翻译为中文比较难找到一个合适的词汇,他的大意是指在运行期使用“合并”的方式修改对象的行为。比如对象obj的类型为A,在运行时将类型B的所有属性和方法“混入”到对象obj上,使得对象obj同时具备类型A和B的属性和行为,就好像obj同时继承了A和B。在动态语言中比较容易实现这个效果,比如python本身支持多继承,还可以通过__bases__动态修改基类,所以python中使用mixin技术是非常简单的,包括javascript也很容易实现这个效果。关于mixin概念的详细说明参考 wikipedia
使用DP我们可以在C#中实现mixin,示例如下:
public interface InterfaceA { void ActionA(); } public class ClassA : InterfaceA { public void ActionA() { Console.WriteLine("I'm from ClassA"); } } public class ClassB { public virtual void ActionB() { Console.WriteLine("I'm from ClassB"); } }测试代码:
ProxyGenerator generator = new ProxyGenerator(); var options = new ProxyGenerationOptions(); options.AddMixinInstance(new ClassA()); ClassB objB = generator.CreateClassProxy运行结果:(options, new CallingLogInterceptor()); objB.ActionB(); InterfaceA objA = objB as InterfaceA; objA.ActionA(); Console.ReadKey();
可以看到代理对象同时具备了ClassA和ClassB的行为
导出、生成代理类型
Castle Dynamic Proxy允许我们将运行时生成的代理类型生成dll文件存到磁盘上,下次启动时通过加载这个dll文件可以避免动态生成代理类型
var scope = new ModuleScope( true, ModuleScope.DEFAULT_ASSEMBLY_NAME, ModuleScope.DEFAULT_FILE_NAME, "DynamicProxyTest.Proxies", "DynamicProxyTest.Proxies.dll"); var builder = new DefaultProxyBuilder(scope); var generator = new ProxyGenerator(builder); var options = new ProxyGenerationOptions(new InterceptorFilter()) { Selector = new InterceptorSelector() }; SimpleSamepleEntity entity = generator.CreateClassProxy注意:上面用到的拦截器和其他测试类都必须加上[Serializable]属性( options, new SimpleLogInterceptor(), new CallingLogInterceptor()); IStorageNode node = generator.CreateInterfaceProxyWithTargetInterface ( new StorageNode("master") , new DualNodeInterceptor(new StorageNode("slave")) , new CallingLogInterceptor()); options = new ProxyGenerationOptions(); options.AddMixinInstance(new ClassA()); ClassB objB = generator.CreateClassProxy (options, new CallingLogInterceptor()); scope.SaveAssembly(false);
可以用reflector查看生成的dll,大致了解代理对象是如何工作的
启动时,可以使用
scope.LoadAssemblyIntoCache(assembly);
将生成的代理类型加载到内存中,其中assembly需要我们手动加载
参考:
Castle Dynamic Proxy Tutorial
Castle's DynamicProxy for .NET