《设计模式之禅》样章连载7:代理的个性和动态代理

来源:互联网 发布:怎样修改mac用户名 编辑:程序博客网 时间:2024/05/17 06:11

12.4.3 代理是有个性的

一个类可以实现多个接口,完成不同任务的整合,那也就是说代理类不仅仅可以实现主题接口,也可以实现其他接口完成不同的任务,而且代理的目的是在目标对象方法的基础上作增强,这种增强的本质通常就是对目标对象的方法进行拦截和过滤,例如游戏代理是需要收费的,升一级需要5元钱,这个计算功能就是代理类的个性,它应该在代理的接口中定义,如图12-6所示。

图12-6       代理类的个性

增加了一个IProxy接口,其作用是计算代理的费用,否则代理公司不是亏死了,我们先来看IProxy接口,如代码清单12-19所示。

代码清单12-19  代理类的接口

public interface IProxy {

     //计算费用

     public voidcount();

}

仅仅一个方法,非常简单,看GamePlayerProxy来的变化,如代码清单12-20所示。

代码清单12-20  代理类

public class GamePlayerProxy implements IGamePlayer,IProxy {

     private IGamePlayergamePlayer = null;       

     //通过构造函数传递要对谁进行代练

     publicGamePlayerProxy(IGamePlayer _gamePlayer){

            this.gamePlayer= _gamePlayer;

     }

     //代练杀怪

     public voidkillBoss() {

            this.gamePlayer.killBoss();

     }

     //代练登录

     public voidlogin(String user, String password) {

            this.gamePlayer.login(user,password);

     }

     //代练升级

     public voidupgrade() {

            this.gamePlayer.upgrade();

            this.count();

     }

     //计算费用

     public voidcount(){

            System.out.println("升级总费用是:150");

     }

}

实现了IProxy接口,同时在upgrade方法中调用该方法,完成费用结算,其他的类都没有任何改动,运行结果如下:

开始时间是:2009-8-2510:45

登录名为zhangSan的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

升级总费用是:150

结束时间是:2009-8-2603:40

好了,代理公司也赚钱了,我的游戏也升级了,皆大欢喜。代理类不仅仅是都可以有自己的运算方法,通常的情况下代理的职责并不一定单一,它可以组合其他的真实角色,也可以实现自己的职责,比如计算费用。代理类可以为真实角色预处理消息、过滤消息、消息转发、事后处理消息等功能,当然一个代理类,可以代理多个真实角色,并且真实角色之间可以有耦合关系,读者可以自行扩展一下。

12.4.4 动态代理

放在最后讲的一般都是压轴大戏,动态代理就是如此,上面的章节都是一个引子,动态代理才是重头戏。嘛是动态代理?动态代理是在实现阶段不用关心代理谁,而在运行阶段才指定代理那一个对象,相对的类说,自己写代理类的方式就是静态代理。本章节的核心部分就在动态代理上,现在有一个非常流行的名称叫做:面向横切面编程,也就是AOP(Aspect Oriented Programming),其核心就是采用了动态代理机制,既然这么重要,我们就来看看动态代理是如何实现的,还是以打游戏为例,类图修改一下以实现动态代理,如图12-7所示。

图12-7       动态代理

在类图中增加了一个InvocationHanlder接口和DynamicProxy类,作用就是产生一个对象的代理对象,其中InvocationHanlder是JDK提供的动态代理接口,对被代理类的方法进行代理。我们来看程序,接口保持不变,实现类也没有变化,请参考代码清单12-1、12-2所示。我们来看DynamicProxy类,如代码清单12-21所示。

代码清单12-21  动态代理类

public class DynamicProxy implements InvocationHandler {

     //被代理者

     Class cls =null;

     //被代理的实例

     Object obj = null;

     //我要代理谁

     public DynamicProxy(Object_obj){

            this.obj =_obj;

     }

     //调用被代理的方法

     public Objectinvoke(Object proxy, Method method, Object[] args)

                    throwsThrowable {

            Objectresult = method.invoke(this.obj, args);

            returnresult;

     }

}

其中invoke方法是接口InvocationHandler定义必须实现的,它完成对真实方法的调用。我们来详细讲解一下InvocationHanlder接口,动态代理是根据被代理的接口生成所有的方法,也就是说给定一个接口,动态代理会宣称“我已经实现该接口下的所有方法了”,那各位读者想想看,动态代理怎么才能实现被代理接口中的方法呢?默认情况下所有的方法返回值都是空的,是的,代理已经实现它了,但是没有任何的逻辑含义,那怎么办?好办,通过InvocationHandler接口,所有方法都由该Handler来进行处理,即所有被代理的方法都由InvocationHandler接管实际的处理任务。

我们接下来看看场景类,如代码清单12-22所示。

代码清单12-22  动态代理的场景类

public class Client {

 

     public static voidmain(String[] args) throws Throwable  {

            //定义一个痴迷的玩家

            IGamePlayerplayer = new GamePlayer("张三");         

            //然后再定义一个代练者

            DynamicProxyproxy = new DynamicProxy(player);              

            //开始打游戏,记下时间戳

            System.out.println("开始时间是:2009-8-25 10:45");            

            String str[]= {"zhangSan","password"};

            Class type[]= {String.class,String.class};

            proxy.invoke(null,player.getClass().getMethod("login", type),str);

            //开始杀怪

            proxy.invoke(null,player.getClass().getMethod("killBoss", null),null);

            //升级

            proxy.invoke(null,player.getClass().getMethod("upgrade", null),null);

            //记录结束游戏时间

            System.out.println("结束时间是:2009-8-26 03:40");

     }

}

很奇怪是吗?不要着急,学习是一个循序渐进的过程,继续看下去,我知道你的疑惑了。其运行结果如下:

开始时间是:2009-8-2510:45

登录名为zhangSan的用户 张三登录成功!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-2603:40

我们还是让代练者帮我们打游戏,但是我们既没有创建代理类,也没有实现IGamePlayer接口,这就是动态代理。别急,动态代理可不仅仅就这么多内容,还有更重要的,如果我们想在游戏登陆后发一个信息给我,防止账号被人盗用嘛,该怎么处理?直接修改被代理类GamePlayer?这不是一个好办法,好办法如代码清单12-23所示。

代码清单12-23  修正后的动态代理

public class DynamicProxy implements InvocationHandler {

     //被代理者

     Class cls =null;

     //被代理的实例

     Object obj = null;    

     //我要代理谁

     publicDynamicProxy(Object _obj){

            this.obj =_obj;

     }

     //调用被代理的方法

     public Objectinvoke(Object proxy, Method method, Object[] args)

                    throwsThrowable {

            Objectresult = method.invoke(this.obj, args);

            //如果是登录方法,则发送信息

             if(method.getName().equalsIgnoreCase("login")){

                    System.out.println("有人在用我的账号登陆!");

            }

            returnresult;

     }

}

看黑体部分,只要在代理中增加一个判断就可以决定是否要发送信息,运行结果如下:

开始时间是:2009-8-2510:45

登录名为zhangSan的用户 张三登录成功!

有人在用我的账号登陆!

张三在打怪!

张三 又升了一级!

结束时间是:2009-8-2603:40

That’s very nice。 有人用我的账号就发送一个信息,然后看看自己的账号是不是被人盗了,非常好的方法,这就是AOP编程,AOP编程没有使用什么新的技术,但是它对我们的设计、编码有非常大的影响,对于日志、事务、权限等都可以在系统设计阶段不用考虑,而在设计后通过AOP的方式切过去。既然动态代理是如此的诱人,我们来看看通用动态代理模型,类图如图12-8所示。


图12-8       动态代理通用类图

很简单,两条独立发展的线路,动态代理实现代理的职责,业务逻辑Subject实现相关的逻辑功能,两者之间没有必然的相互耦合的关系,最终在高层模块也就是Client进行耦合,完成逻辑的封装任务,我们先来看Subject接口,如代码清单12-24所示。

代码清单12-24  抽象主题

public interface Subject { 

     //业务操作

     public voiddoSomething(String str);

}

其中的doSomething是一个标示方法,可以有多个逻辑处理方法,实现类如代码清单12-25所示。

代码清单12-25  真实主题

public class RealSubject implements Subject {

     //业务操作

     public voiddoSomething(String str) {

            System.out.println("dosomething!---->" + str);

     }

}

重点是我们的MyInvocationHandler,如代码清单12-26所示。

代码清单12-26  动态代理的Handler

public class MyInvocationHandler implements InvocationHandler{

     //被代理的对象

     private Objecttarget = null;

     //通过构造函数传递一个对象

     publicMyInvocationHandler(Object _obj){

            this.target= _obj;

     }

     //代理方法      

     public Objectinvoke(Object proxy, Method method, Object[] args)

                    throwsThrowable {

            //设置返回值

            Objectresult = null;

            //前置通知

            this.before();

            //执行被代理的方法

            result =method.invoke(this.target, args);

            //后置通知

            this.after(); 

            //返回值

            returnresult;

     }

     //前置通知

     public voidbefore(){

            System.out.println("执行before方法");

     }

     //后置通知

     public voidafter(){

             System.out.println("执行after方法");

     }

}

上面的代码注释如果看不懂也不要紧,什么是前置通知,什么是后置通知,这是AOP术语,我们先把程序看完整,然后再分析,DynamicProxy代码如代码清单12-27所示。

代码清单12-27  动态代理类

public class DynamicProxy {

     //定义要代理哪个类

     private Object obj=null;

     //通过构造函数传递被代理对象      

     publicDynamicProxy(Object _obj){

            Class c =_obj.getClass();

            //生成被代理类的代理类

            this.obj =Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), newMyInvocationHandler(_obj));

     }

     //执行代理类的方法

     public Objectexec(String methodName,Object...args){

            //返回值

            Objectresult = null;

            //方法中的参数类型

            Class[] c=new Class[args.length];

            int i=0;

            //获得参数的类型

            for(Objecto:args){

                    c[i]= o.getClass();

                    i++;

            }

            try {

                    //根据方法名称和参数类型查找到唯一一个方法

                    Methodmethod=this.obj.getClass().getMethod(methodName, c);

                    //执行该方法

                    result= method.invoke(this.obj, args);

            } catch(Exception e) {

                    e.printStackTrace();

            }

            returnresult;

     }

}

最后就是看我们怎么调用了,如代码清单12-28所示。

代码清单12-28  动态代理的场景类

public class Client {

    

     public static voidmain(String[] args) {

            DynamicProxyproxy = new DynamicProxy(new RealSubject());

            String[] str= {"1111"};

            proxy.exec("doSomething",str);

     }

}

运行结果如下所示:

执行before方法

do something!---->1111

执行after方法

好,所有的程序都看完了,我们回过头来看看程序是怎么实现的。在MyInvocationHandler中,我们设计了两个方法before和after方法,这两个方法分别叫做了前置通知和后置通知,它是指在被委托或代理的类的方法执行前后的顺序关系,你看我们的运行结果,先执行before方法,然后执行被代理的方法,然后执行after方法,这也正是我们需要的结果,这就是AOP编程的核心,我们一不小心就进入了一个新的编程模型中。在DynamicProxy类中,我们有这样的方法:

this.obj = Proxy.newProxyInstance(c.getClassLoader(),c.getInterfaces(), new MyInvocationHandler(_obj));

该方法是重新生成了一个对象,为什么要重新生成?你要使用代理呀,注意c.getInterfaces()这句话,这是非常有意思的一句话,是说查找到该类的所有接口,然后实现接口的所有方法,当然了,方法都是空的,由谁具体负责接管呢?是newMyInvocationHandler(_Obj)这个对象,于是清楚了:一个类的动态代理类是这样的一个类,由InvocationHandler的实现类实现所有的方法,由其invoke方法接管所有方法的实现,也就是每个方法的调用都要经过invoke过滤一遍。

注意  要实现动态代理的首要条件是:被代理类必须实现一个接口,回想一下刚刚的分析吧。当然了,现在也有很多技术如CGLIB可以实现不需要接口也可以实现动态代理的方式。

再次说明,以上的动态代理是一个通用代理框架,如果你想设计自己的AOP框架,完全可以在此基础上扩展,我们设计的是一个通用代理,只要有一个接口,一个实现类,就可以使用该代理,完成代理的所有功效。

12.5 最佳实践

       我相信代理模式大家使用的已经不多了,为什么呢?有了AOP我相信大家都懒得去写代理了,而且也已经没有必要,有类似Spring AOPAspectJ这样非常优秀的工具,我们还自己写动态代理干嘛!不过,大家可以看看源代码,特别是调试时,只要看到类似$Proxy0这样的结构,你就应该知道这是一个动态代理了。

       友情提醒,在学习AOP框架时,弄起初几个名词就成:切面(Aspect)、切入点(JoinPoint)、通知(Advice)、植入(Wave)就足够了,理解了这几个名词,你就可以对AOP游刃有余了!

《设计模式之禅》目录
《设计模式之禅》前言
《设计模式之禅》样章连载1:原型模式之“个性化电子账单”
《设计模式之禅》样章连载2:原型模式的定义及应用
《设计模式之禅》样章连载3:原型模式的注意事项
《设计模式之禅》样章连载4:代理模式之“我是游戏至尊”
《设计模式之禅》样章连载5:代理模式的定义及应用
《设计模式之禅》样章连载6:代理模式扩展之“普通代理”和“强制代理”
《设计模式之禅》试读员招募(免费申领样书)

原创粉丝点击