Richie

Sometimes at night when I look up at the stars, and see the whole sky just laid out there, don't you think I ain't remembering it all. I still got dreams like anybody else, and ever so often, I am thinking about how things might of been. And then, all of a sudden, I'm forty, fifty, sixty years old, you know?

Castle Dynamic Proxy - 2.2

项目地址:Castle Dynamic Proxy
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<SimpleSamepleEntity>(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<SimpleSamepleEntity>(
    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<IInterceptor>();
    }
}
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<SimpleSamepleEntity>(
    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<IStorageNode>(
    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<ClassB>(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<SimpleSamepleEntity>(
    options,
    new SimpleLogInterceptor(), new CallingLogInterceptor());
IStorageNode node = generator.CreateInterfaceProxyWithTargetInterface<IStorageNode>(
    new StorageNode("master")
    , new DualNodeInterceptor(new StorageNode("slave"))
    , new CallingLogInterceptor());
options = new ProxyGenerationOptions();
options.AddMixinInstance(new ClassA());
ClassB objB = generator.CreateClassProxy<ClassB>(options, new CallingLogInterceptor());
scope.SaveAssembly(false);
注意:上面用到的拦截器和其他测试类都必须加上[Serializable]属性
可以用reflector查看生成的dll,大致了解代理对象是如何工作的
启动时,可以使用
scope.LoadAssemblyIntoCache(assembly);
将生成的代理类型加载到内存中,其中assembly需要我们手动加载

参考:
Castle Dynamic Proxy Tutorial
Castle's DynamicProxy for .NET

posted on 2010-03-15 22:46  riccc  阅读(12191)  评论(9编辑  收藏  举报

导航