Leo Zhang

A simple man with my own ideal

Castle动态代理技术初探

一、需求的提出

    假设朋友给我一个CalculatorLib.dll文件,里面包含了一个计算器接口和一个实现了该接口的计算器类,我的程序里要用到这个计算器来计算两个整数的和(仅作为简单例子,不考虑溢出处理等其他方面),计算器的实现大概如下:

    public interface ICalculator
    {
        Int32 AddOperation(Int32 p1, Int32 p2);
    }

    public class Calculator : ICalculator
    {
        public virtual Int32 AddOperation(Int32 p1, Int32 p2)
        {
            //①
            //②
            return p1 + p2;
            //③
        }
    }

    我的需求是:想在①这里为代码赋予修改输入参数和返回值的权限,在②更改参数和返回值,在③这里收回该权限,分两种情况:

     1、我手里有源代码

     我会去直接改AddOperation方法,引入权限控制的程序集,在①和③处加入权限控制代码,在②处加入修改输入参数的代码,也许还要重新写一下return后边的东西,基本上这样做就能够满足要求;

    2、我手里只有这个dll没有源代码

     这时候我的主程序要引入权限控制的程序集,然后写一个方法,让这个方法在调用AddOperation之前做权限赋予和参数修改,在调用AddOperation之后做权限收回。

     看看缺点:都需要引入权限控制的程序集后将权限控制和纯计算逻辑放在一起,不满足单一职责原则;扩展性也不太好;如果计算器类中还有减、乘除等很多方法,而每个方法都需要有权限的赋予和收回,这就意味着会有大量重复代码:赋予权限和收回权限。

怎么办呢?其实我们可以利用代理模式把计算器类改造一下,当然前提是AddOperation必须为虚函数,如下:

    public class CalculatorProxy : Calculator
    {
        public override Int32 AddOperation(Int32 p1, Int32 p2)
        {
            Int32 pa1 = p1;
            Int32 pa2 = p2;
            this.GrantPermission();
            this.ModifyParams(ref pa1, ref pa2);
            return this.ModifyReturnValue(this.InvokeMethod(pa1, pa2));
        }

        private Int32 InvokeMethod(Int32 p1, Int32 p2)
        {
            return  base.AddOperation(p1, p2);
        }

        private void GrantPermission() { }

        private void RevokePermission() { }

        private void ModifyParams(ref Int32 p1, ref Int32 p2)
        {
            p1 = 2;
            p2 = 1;
        }

        private Int32 ModifyReturnValue(Int32 value)
        {
            return value + 1;
        }
    }

 

   

     这么写似乎好点,对权限程序集的依赖被转移到了代理类当中,如果所有算术计算方法都需要订制自己的权限控制策略,那么我们就要为它们增加各自的GrantPermission和RevokePermission方法,代码似乎不太优雅而且最好能在我们需要的时候执行权限控制,让我们的精力放在核心业务逻辑上;最后,上面的代理类处理方式有一个固定的模式,调用方法前准备活动——>调用目标方法——>调用结束后处理活动,让我们想到策略模式。

二、一种解决方案

     要较好的实现上面的需求,可以使用动态代理技术。在动态代理方面我觉得开源项目Castle做的挺好,朴实无华,在它动态生成的类中甚至连像委托(delegate)这样稍复杂的东西都没用到。

    下面以Castle的动态代理框架:DynamicProxy做为工具,涉及到的dll有:Castle.Core.dll和Castle.DynamicProxy2.dll。

先给出例子的源码:

    class Program
    {
        static void Main(string[] args)
        {
            String path = AppDomain.CurrentDomain.BaseDirectory;
            ModuleScope scope = new ModuleScope(true, "Invocation", path + "\\Invocation.dll", "Proxy", path + "\\Proxy.dll");
            DefaultProxyBuilder builder = new DefaultProxyBuilder(scope);
            ProxyGenerator pg = new ProxyGenerator(builder);
            ICalculator cal = pg.CreateClassProxy(typeof(Calculator), new PermissionInterceptor(), new ModifyInterceptor()) 
                              as ICalculator;

                        

                   Console.WriteLine("待计算表达式为:+ 3 = ?,方法最初传入参数为:(1,1)\n");

                   Console.WriteLine("表达式:1 + 3 = {0}\n",cal.AddOperation(1,1));

                   scope.SaveAssembly(true); //加这句话可以将动态生成的Invocation类保存到本地硬盘

                   scope.SaveAssembly(false); //加这句话可以将动态生成的Proxy类保存到本地硬盘


             Console.ReadKey();
        }
    }

    public class PermissionInterceptor : StandardInterceptor
    {
        protected override void PreProceed(IInvocation invocation)
        {
            Console.WriteLine("申请更改参数和返回值的权限....\n");
        }

        protected override void PostProceed(IInvocation invocation)
        {
            Console.WriteLine("收回更改参数和返回值的权限....\n");
        }
    }

    public class ModifyInterceptor : StandardInterceptor
    {
        protected override void PreProceed(IInvocation invocation)
        {
            Console.WriteLine("----------------------Begin----------------------------\n");
            Console.WriteLine("对参数值进行拦截\n");
            invocation.SetArgumentValue(0, 2);
            invocation.SetArgumentValue(1, 1);
            Console.WriteLine("方法参数为:(2,1)\n");
            Console.WriteLine("----------------------End----------------------------\n");
        }

        protected override void PostProceed(IInvocation invocation)
        {
            Console.WriteLine("----------------------Begin----------------------------\n");
            Console.WriteLine("对返回值进行拦截并加1\n");
            invocation.ReturnValue = Int32.Parse(invocation.ReturnValue.ToString()) + 1;
            Console.WriteLine("----------------------End----------------------------\n");
        }
    }

    其中的PermissionInterceptor和ModifyInterceptor为自定义拦截器,前者用作权限控制,后者用作参数和返回值更改。

     1、熟悉一下IInvocation接口,我更愿意把实现了该接口的类型叫做一个上下文环境,其中包含的被代理方法的参数、元数据、返回值、返回类型等信息,类似于这样:

   public interface IInvocation
    {
       
object GetArgumentValue(int index);
        MethodInfo
GetConcreteMethod();
        MethodInfo
GetConcreteMethodInvocationTarget();
        void
Proceed();
        void
SetArgumentValue(int index, object value);

        object[]
Arguments { get; }
       
Type[] GenericArguments { get; }
        object
InvocationTarget { get; }
        MethodInfo
Method { get; }
        MethodInfo
MethodInvocationTarget { get; }
        object
Proxy { get; }
        object
ReturnValue { get; set; }
       
Type TargetType { get; }
    }

     2、是拦截器接口:IInterceptor和一个标准拦截器:StandardInterceptor;IInterceptor里面就一个方法:

   void Intercept(IInvocation invocation)

     StandardInterceptor如下,一个典型的策略模式,由于拦截器继承了MarshalByRefObject,所以它的实例不会离开创建它的AppDomain,其他AppDomain中的实例

会以remoting的方式使用它,另外拦截器需要的参数只有IInvocation,一般来说我们要通过继承标准拦截器来自定义拦截器。

    public class StandardInterceptor : MarshalByRefObject, IInterceptor
    {
        public void Intercept(IInvocation invocation)
        {
            this.PreProceed(invocation);
            this.PerformProceed(invocation);
            this.PostProceed(invocation);
        }

        protected virtual void PerformProceed(IInvocation invocation)
        {
            invocation.Proceed();
        }

        protected virtual void PostProceed(IInvocation invocation)
        {
        }

        protected virtual void PreProceed(IInvocation invocation)
        {
        }
    }

    3、Castle为我们动态生成的类型有:代理类、实现了IInvocation接口的上下文环境,一下代码为Reflector反编译结果,像methodof理解为从元数据表中获取指定类型的指定方法的元数据信息(注:使用Castle官网提供的“Release Candidate 3 - September 20, 2007”下的SourceCode生成的Proxy类和Invocation类与“Monorail 2 - January 17th, 2010”下的release版本生成的类有所不同,如,前者生成的Invocation类是作为Proxy类的内嵌类实现的,以下生成代码使用的是后者):

    CalculatorProxy类:

    [Serializable, XmlInclude(typeof(Calculator))]
    public class CalculatorProxy : Calculator, IProxyTargetAccessor
    {
        [XmlIgnore]
        public IInterceptor[] __interceptors;
        public static ProxyGenerationOptions proxyGenerationOptions;
        public static MethodInfo token_AddOperation = ((MethodInfo)methodof(Calculator.AddOperation, Calculator));

        public CalculatorProxy()
        {
            this.__interceptors = new IInterceptor[] { new StandardInterceptor() };
        }

        public CalculatorProxy(IInterceptor[] interceptorArray1)
        {
            this.__interceptors = interceptorArray1;
        }

        public override int AddOperation(int p1, int p2)
        {
            Calculator_AddOperation operation = new Calculator_AddOperation(typeof(Calculator), this, this.__interceptors, token_AddOperation, new object[] { p1, p2 });
            operation.Proceed();
            return (int)operation.ReturnValue;
        }

        public override int AddOperation_callback(int p1, int p2)
        {
            return base.AddOperation(p1, p2);
        }

        public override object DynProxyGetTarget()
        {
            return this;
        }

        public override IInterceptor[] GetInterceptors()
        {
            return this.__interceptors;
        }
    }

     ② Calculator_AddOperation类(Invocation)

    [Serializable]
    public class Calculator_AddOperation : InheritanceInvocation
    {
        // Methods
        public Calculator_AddOperation(Type type1, object obj1, IInterceptor[] interceptorArray1, MethodInfo info1, object[] objArray1)
            : base(type1, obj1, interceptorArray1, info1, objArray1)
        {
        }

        public override void InvokeMethodOnTarget()
        {
            int num = (base.proxyObject as CalculatorProxy).AddOperation_callback((int)base.GetArgumentValue(0), (int)base.GetArgumentValue(1));
            base.ReturnValue = num;
        }
    }

     Calculator_AddOperation类间接实现了一个抽象类AbstractInvocation,我们只看其中的Proceed方法:

    public void Proceed()
    {
        if (this.interceptors == null)
        {
            this.InvokeMethodOnTarget();
        }
        else
        {
            this.execIndex++;
            if (this.execIndex == this.interceptors.Length)
            {
                this.InvokeMethodOnTarget();
            }
            else
            {
                if (this.execIndex > this.interceptors.Length)
                {
                    string str;
                    if (this.interceptors.Length > 1)
                    {
                        str = " each one of " + this.interceptors.Length + " interceptors";
                    }
                    else
                    {
                        str = " interceptor";
                    }
                    throw new InvalidOperationException(string.Concat(new object[] { "This is a DynamicProxy2 error:”+ 
                    "invocation.Proceed() has been called more times than expected.This usually signifies a bug in the"+
                    "calling code. Make sure that", str, " selected for this method '", this.Method, "'calls invocation.Proceed() at most once." }));
                }
                this.interceptors[this.execIndex].Intercept(this);
            }
        }
    }

    其中execIndex便是当前正在使用的拦截器(interceptor),通过this.interceptors[this.execIndex].Intercept(this);执行当前拦截器的PreProceed方法后触发下一个拦截器,这是一种嵌套的触发方式,实际工作流程如下图所示:

    实际上从上面代码也可以看出:CalculatorProxy类与Calculator_AddOperation类是互相知道的,所以用Reflector看,它们程序集之间是互引用的,如图:

 

 

    通过Castle的动态代理框架可以很方便的进行AOP编程,需要注意的是被代理类里面需要被拦截的方法必须是虚方法,不同的虚方法可以共用相同的拦截器。

三、总结

    初学Castle动态代理技术,让我钦佩这些开源项目的作者,钦佩的同时意识到需要努力提高自己,学习依然在进行,希望能和园子里的朋友们一起分享。
 本文源代码

posted on 2010-03-04 22:28  Leo Zhang  阅读(3929)  评论(18编辑  收藏

导航