一、需求的提出
假设朋友给我一个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) { .WriteLine("----------------------Begin----------------------------\n"); .WriteLine("对参数值进行拦截\n"); invocation.SetArgumentValue(0, 2); invocation.SetArgumentValue(1, 1); .WriteLine("方法参数为:(2,1)\n"); .WriteLine("----------------------End----------------------------\n"); } protected override void PostProceed(IInvocation invocation) { .WriteLine("----------------------Begin----------------------------\n"); .WriteLine("对返回值进行拦截并加1\n"); invocation.ReturnValue = Int32.Parse(invocation.ReturnValue.ToString()) + 1; .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动态代理技术,让我钦佩这些开源项目的作者,钦佩的同时意识到需要努力提高自己,学习依然在进行,希望能和园子里的朋友们一起分享。本文源代码