动态代理 — 代理模式的最高境界
和往常一样,小吴最后一个来到工位上,用脚点开主机的按钮,伴随着主机箱里传出的卡车启动般的轰轰声,一天的快乐摸鱼时光又开始了......
点开腾讯体育新闻,小吴正准备看看昨晚NBA的战况如何。突然,小吴的耳朵一阵警觉,似曾相识的脚步声越来越近,,,小吴心想:难道,我上班摸鱼被老板发现了??,,关掉手机正在播放的NBA精彩十佳球,小吴若无其事地缓缓抬起头,把目光挪向眼前的电脑屏幕,眉间一皱,左手键盘,右手鼠标,毫无破绽,俨然一副认真搬砖的样子。
脚步声随即不出所料的停在小吴的跟前,抬头一瞧,原来是项目经理大勇,小吴明白这是来活了。
“小吴,客户有个新需求,XX项目客户需要记录每一个业务操作的时间和操作人信息,你来做一下这个功能吧,客户希望明天就能上线这个功能。”
“哦。。哦。。。好的。”,小吴知道今天注定不会是快乐摸鱼的一天,但是为了尽快能摸上鱼,小吴快速的打开了XX项目,开始分析需求......
项目的原始业务代码是这样的:
1 //业务类接口 2 public interface IBLLClass 3 { 4 void DoThing1(); 5 void DoThing2(); 6 } 7 8 //业务类 9 public class BLLClass : IBLLClass 10 { 11 public void DoThing1() 12 { 13 //DoThing1的业务逻辑 14 Console.WriteLine("执行DoThing1..."); 15 } 16 17 public void DoThing2() 18 { 19 //DoThing2的业务逻辑 20 Console.WriteLine("执行DoThing2..."); 21 } 22 23 }
如果是一年前的小吴看到这样的代码,二话不说,他会直接往业务类中硬生生地插入要增加的功能,就像这样:
1 //业务类 2 public class BLLClass : IBLLClass 3 { 4 public void DoThing1() 5 { 6 LogUserOperation(); 7 8 //DoThing1的业务逻辑 9 } 10 11 public void DoThing2() 12 { 13 LogUserOperation(); 14 15 //DoThing2的业务逻辑 16 } 17 18 private void LogUserOperation() 19 { 20 //记录每一个业务操作的时间和操作人信息 21 } 22 }
但是,这时的小吴脑子里想到的是他在某一本讲设计模式的书上看到的那句话:尽量避免编写对原系统代码侵入性强的代码。
何谓侵入性强的代码,以上的代码就是一个很好的例子:它直接将需要新增的业务代码插入到原来的业务代码中。这就带来了一个问题,如果你是用这种方法频繁地去扩展你的业务代码,那么过不了多久,你就很难再理清你原来最基本的业务逻辑是什么样的了,到最后会发现自己的代码面目全非。
所以,懒惰又聪明地小吴很快就想起了前不久在书上看到的代理模式,说时迟,那时快,小吴在键盘上“咔咔咔”一顿骚操作,以迅雷不及掩耳之势敲完了代码,哼,单身22年的手速可不是盖的,你懂得,嘿嘿嘿。。。额。。跑题了。。。咱们回头来看看小吴写的代码有多骚:
1 //业务代理类 2 public class BLLClassProxy : IBLLClass 3 { 4 private IBLLClass bllClass = new BLLClass(); 5 6 public void DoThing1() 7 { 8 //添加的业务逻辑 9 LogUserOperation(); 10 11 //调用原始逻辑 12 bllClass.DoThing1(); 13 } 14 15 public void DoThing2() 16 { 17 LogUserOperation(); 18 19 bllClass.DoThing2(); 20 } 21 22 private void LogUserOperation() 23 { 24 //记录每一个业务操作的时间和操作人信息 25 } 26 }
如果你认真看过上面的代码就会明白,BLLClassProxy这个类实现了BLLClass中所具备的所有业务函数,而且最终的业务逻辑都是来自对BLLClass类中业务函数的调用,BLLClassProxy类完全可以代替BLLClass类,而且在BLLClassProxy中还可以任意增加额外的业务逻辑,这很好的实现了我们需要的效果:对原始代码没有侵入性,且达到扩展业务逻辑的目的。我们把BLLClassProxy称作为BLLClass的代理类,顾名思义,前者代理了后者所有的业务逻辑,故称之为代理类。
那么我们将会在实际的调用代码中做如下改动:
1 IBLLClass bll = new BLLClass(); //==> 修改前:使用原始业务类 2 3 IBLLClass bll = new BLLClassProxy(); //==> 修改后:使用代理业务类
我们看到,由于原始业务类(BLLClass)与代理业务类(BLLClassProxy)都实现了接口IBLLClass,所以只需要将业务类声明变量切换成代理类的实现就完成了整个需求的变动,无需做其他改动。当然,这还要得益于业务类的创建者的先见之明,提供了一个业务类的接口(IBLLClass),这也同时给我们提了个醒,尽量以接口的方式声明业务类变量,就是所谓的“面向接口编程”。
以上就是使用了代理模式来解决业务扩展的需求,其实再按细分的话,我们可以将以上的代理叫做 静态代理。静态代理指的就是在代码编译阶段就已经生成代理类,而你需要在程序运行前就将代理类的代码一五一十地写好,如果有五十个代理类就写五十个,有一百个就写一百个。有人会问,啥?难不成还能有动态代理?当然,继续看剧情发展......
懒人都爱用的“动态代理”
项目经理大勇又一次笑眯眯地走向小吴,小吴不禁心头一紧,心想,不妙。
“小吴啊,客户又提了几个需求,不过不用慌,和上次要加的那个需求一样,只不过这次把另外那20个业务类也加上同样的业务逻辑。”
小吴心中即便一万个草泥马,但是却依然面带微笑,点头“好好好”。
大勇也不忘夸小吴两句:“上回加的那个代理类很不错嘛,就照着那种方法加,很快的”。
“很快?要不你来试试?”,小吴一边嘴里嘀咕着,一边在脑子里又蹦出了个新想法。
又要手撸20多个代理类?有没有什么别的方法能减少我的工作量呢,比如。。。自动生成代理类?
年轻人就怕没想法,有想法就要付诸于行动,于是小吴开始寻思着如何实现自己想法。
自动生成代理类?从解决问题的思路出发有两种解决方法:
1、写一个代码生成器,生成代理类的代码,然后再进行编译。这种方法本质上还是我们上面介绍的静态代理类,只不过不用我们手动编写代理类的代码,但是使用这种方法是极其痛苦的,虽然你不用手动编写真正的代理类代码,但是你必须去做一些代码生成的配置吧,这个过程是及其繁琐的,一个字概括,“太他妈LOW了!”
2、在程序编译好后跑起来的过程中,动态生成代理类。说的是什么?程序都已经跑起来了还能生成代理类?不错,这就是动态生成代理类,借助的是c#语言的Emit高级特性。
显然我们需要的是动态生成代理类,那么这么说的话我们还必须要再学一学Emit创建类?小吴觉得照这样下去这事儿得黄了,这已经不是临阵磨枪了,这是要临阵造轮子啊,这活还干不干了,等着造完这个轮子,黄花菜都凉了。
于是当天晚上祖师爷就托梦给小吴,让小吴用一用 Castle 框架,如获至宝的小吴第二天就按照祖师爷的指示用上了Castle框架,嗯,真香~~
我们在使用技术中大部分都是用既有的轮子,造轮子这种事情让小吴干,小吴可不乐意。当然,造轮子对于我们的学习还是很有用处的,尝试着去造造轮子,会提升你对技术更深层次的理解,因为你不再只是站在使用者的角度简单地看问题。
我们来看看Castle是怎么使用的:
1、首先引用 Castle.Core 的dll文件
2、创建建拦截器,拦截器的意思非常直观,就是把运行着的方法拦下来,等会儿,你先别运行,先执行完我给你的方法A,再运行你自己的方法,然后再运行我给你的方法B......也就是说通过拦截器你可以在原始业务逻辑的前后或者任意可切入的点插入你想加入的业务逻辑。Castle 框架提供了一个标准的拦截器类 StandardInterceptor:
1 public class StandardInterceptor : MarshalByRefObject, IInterceptor 2 { 3 public StandardInterceptor(); 4 5 public void Intercept(IInvocation invocation); 6 protected virtual void PerformProceed(IInvocation invocation); 7 protected virtual void PostProceed(IInvocation invocation); 8 protected virtual void PreProceed(IInvocation invocation); 9 }
你可以重写 PreProceed、PerformProceed、PostProceed 三个虚方法,它们表示三个拦截点:执行前、执行中、执行后,你可以加入自己的业务逻辑到这三个拦截点里,实现业务的添加。
于是像这样建立一个拦截器类:
1 public class LogInterceptor: StandardInterceptor 2 { 3 protected override void PostProceed(IInvocation invocation) 4 { 5 LogUserOperation(); 6 } 7 8 private void LogUserOperation() 9 { 10 //记录每一个业务操作的时间和操作人信息 11 Console.WriteLine("记录日志"); 12 } 13 }
然后这样编写创建业务类变量的代码:
1 //代理生成类 2 ProxyGenerator proxyCreator = new ProxyGenerator(); 3 4 //通过Castle动态生成代理类 5 IBLLClass bll = proxyCreator.CreateInterfaceProxyWithTargetInterface<IBLLClass>(new BLLClass(), new LogInterceptor());
我们可以看到最终业务类实例的创建是借助于Castle框架来生成的,其实Castle框架就替我们完成了动态代理类的生成,我们只需要在拦截器中写好我们需要加入的拦截方法体代码,然后注入到代理类。当然这里只是提供了使用Castle框架一个很简单的例子,其实Castle中还有很多丰富的功能,有待各位看官根据自己的需求去发掘,师傅领进门,修行靠个人嘛,就是这么个道理。最后展示一下运行结果:
动态代理可以称的上是代理模式的最高境界,因为它借助动态生成的技术让程序在运行过程中自动根据注册条件生成代理类,省去了手写静态代理类的麻烦。同时动态代理模式也是AOP(面向切面编程)的一种很好的实现方式。