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方法后触发下一个拦截器,这是一种嵌套的触发方式,实际工作流程如下图所示:

Castle动态代理技术初探

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

Castle动态代理技术初探
 

 

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

三、总结

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

你可能感兴趣的:(动态代理)