转载

神秘代理-Proxy

前言:

代理模式作为常见的设计模式之一,在项目开发中不可或缺。本文就尝试着揭开代理的神秘面纱,也欢迎各路人批评指正!

1.如何实现代理:

【假设有个关于汽车移动(move)的计时需求】设计:Moveable接口,一个Car的实现类;两个代理CarTimer,TimeHandler.UML图如下:

神秘代理-Proxy 1)继承

神秘代理-Proxy
 1 package com.gdufe.proxy;  2   3 import java.util.Random;  4   5 public class CarTimer extends Car {  6       7     @Override  8     public void move() {  9         long start=System.currentTimeMillis(); 10         super.move();        //调用父类的move()方法 11         try{ 12             Thread.sleep(new Random().nextInt(10000)); 13         }catch(Exception e){ 14             e.printStackTrace(); 15         } 16         long end=System.currentTimeMillis(); 17         System.out.println("I'm time machine.Time for moving:"+(end-start)); 18     } 19 }
View Code


2)组合

神秘代理-Proxy
 1 package com.gdufe.proxy;  2   3 import java.util.Random;  4   5 public class TimeHandler implements Moveable {  6     private Moveable m;  7     public TimeHandler(Moveable m) {  8         this.m = m;  9     } 10     @Override 11     public void move() { 12         long start=System.currentTimeMillis(); 13         m.move(); 14         try{ 15             Thread.sleep(new Random().nextInt(10000)); 16         }catch(Exception e){ 17             e.printStackTrace(); 18         } 19         long end=System.currentTimeMillis(); 20         System.out.println("I'm time machine.Time for moving:"+(end-start)); 21     } 22  23 }
View Code

客户端代码:

 1 package com.gdufe.proxy;  2   3 public class Client {  4     public static void main(String[] args) {  5         System.out.println("继承实现代理:");  6         new CarTimer().move();  7         System.out.println("组合实现代理:");  8         new TimeHandler(new Car()).move();  9     } 10 }


输出结果:

继承实现代理: Car moving... I'm time machine.Time for moving:7080 组合实现代理: Car moving... I'm time machine.Time for moving:5169

分析:从上述例子实现当中,我们第一感觉自然是分不出两种代理的实现方式孰优孰劣。且继续往下面看。

2.灵活代理-接口切换

【假设现在特殊需求不确定:“汽车移动之前先往左还是先往右”】很明显,我们此时若使用继承的方式实现代理,则后续很不容易维护,而且会形成臃肿的继承链;但使用接口的方式我们发现仅需要两个代理类:TurnLeft,TurnRight。而且,不管后续需求如何都只需要做简单的调整。UML图如下:

神秘代理-Proxy

----------

TurnLeft.java

神秘代理-Proxy
 1 package com.gdufe.proxy;  2   3 public class TurnLeft implements Moveable {  4     private Moveable m;  5     public TurnLeft(Moveable m) {  6         this.m = m;  7     }  8     @Override  9     public void move() { 10         System.out.println("turn left..."); 11         m.move(); 12     } 13  14 }
View Code

TurnRight.java

神秘代理-Proxy
 1 package com.gdufe.proxy;  2   3 public class TurnRight implements Moveable {  4   5     private Moveable m;  6     public TurnRight(Moveable m) {  7         this.m = m;  8     }  9     @Override 10     public void move() { 11         System.out.println("turn right"); 12         m.move(); 13     } 14  15 }
View Code

客户端代码:

 1 package com.gdufe.proxy;  2   3 public class Client0 {  4   5     /**  6      * @param args  7      */  8     public static void main(String[] args) {  9         System.out.println("Turn right,then left before moving:"); 10         test1(); 11          12         System.out.println("Turn left,then right before moving:"); 13         test2(); 14     } 15     //对接口的实现内外包装 16     private static void test1() { 17         Car car  = new Car(); 18         Moveable m1 = new TurnLeft(car); 19         Moveable m2 = new TurnRight(m1); 20         m2.move(); 21     } 22     public static void test2(){ 23         Car car = new Car(); 24         Moveable m1 = new TurnRight(car); 25         Moveable m2 = new TurnLeft(m1); 26         m2.move(); 27     } 28  29 }

输出结果:

Turn right,then left before moving: turn right turn left... Car moving... Turn left,then right before moving: turn left... turn right Car moving...

========================

3.动态代理:

其实,不管是继承还是组合的方式,我们上面实现的都仅仅是“静态代理”,也是我们平时用的比较多的。现在,我们开始聊聊Java的神器---“动态代理”。

【假设现在需要实现一个“万能”的日志工具,即不管对任何类的任何方法都可以动态对其进行日志操作】

例如:上面的例子中, 请思考如何实现在汽车移动之前进行日志操作?

常规的静态代理方式当然可以实现,下面我们就利用Java中的Proxy类进行实现。UML图如下: 神秘代理-Proxy

添加关键类:LogHandler.java

 1 package com.gdufe.proxy;  2   3 import java.lang.reflect.InvocationHandler;  4 import java.lang.reflect.Method;  5   6 public class LogHandler implements InvocationHandler {  7       8     //被代理对象  9     private Object proxied; 10      11     public LogHandler(Object proxied) { 12         this.proxied = proxied; 13     } 14     @Override 15     public Object invoke(Object proxy, Method method, Object[] args) 16             throws Throwable { 17         System.out.println("I'm log machine."); 18         return method.invoke(proxied); 19     } 20  21 }

客户端代码:

 1 package com.gdufe.proxy;  2   3 import java.lang.reflect.InvocationHandler;  4 import java.lang.reflect.Proxy;  5   6 public class Client1 {  7   8     /**  9      * @param args 10      */ 11     public static void main(String[] args) { 12          13         //单独创建Moveable接口对象 14         Moveable m=null; 15         m = new Car(); 16         m = new TimeHandler(m); 17          18         //初始化LogHandler实现的InvacationHandler接口 19         InvocationHandler h =  new LogHandler(m); 20         m = (Moveable) Proxy.newProxyInstance( 21                 Moveable.class.getClassLoader(), 22                 new Class[] {Moveable.class},h); 23          24         //m在动态代理处理完后,move()方法已被修改 25         m.move(); 26     } 27  28 }

输出结果:

I'm log machine. Car moving... I'm time machine.Time for moving:110

分析:

上述的实现代码对于刚接触Java的朋友来说估计比较费解。要理解其过程,至少对java的反射机制有一定的理解。看穿了的话,其实动态代理的关键环节,就在newProxyInstance()操作上。代码实现的关键步骤:

Step1:初始化被代理的对象(如上图中的TimeHandler);

Step2:创建一个实现了InvocationHandler接口的类,在invoke()方法进行你希望执行的代理操作(如上图的LogHandler);

Step3:将通过Proxy拿到的新对象赋给最终要实现的接口,最后调用该接口方法(如代码中的“m.move()”)。

---------------------------------------------------------------------

有朋友可能犯迷糊了, 动态代理的内部实现过程呢?Proxy是怎样一步一步识别到Car的呢?

请看图:

神秘代理-Proxy

注意 :$Proxy类是虚拟存在的,在Java API中是找不到的。也就是说,它只存在于中间过程。所以,为方便大家理解就加上了。)

那么,到底动态代理适用于什么情形呢?从上面汽车的简单日志实例也许还难以看出。下面我们再引入一个测试。

【假设现在增加一个司机(Driver)类,其实现了Speakable接口;很明显他跟汽车没有很直接的关联,那么现在我们利用动态代理的方式将上面的LogHandler加到司机的speak()方法上】

----------

客户端代码:

 1 package com.gdufe.proxy;  2   3 import java.lang.reflect.InvocationHandler;  4 import java.lang.reflect.Proxy;  5   6 public class Client2 {  7   8     /**  9      * @param args 10      */ 11     public static void main(String[] args) { 12         Moveable m=null; 13         m = new Car(); 14  15         Speakable s =null; 16         s = new Driver(); 17          18         InvocationHandler h = null; 19         h=new LogHandler(m); 20         m = (Moveable) Proxy.newProxyInstance(Moveable.class.getClassLoader(),new Class[] {Moveable.class},h); 21         m.move(); 22          23         //司机被代理 24         h = new LogHandler(s); 25         s = (Speakable)Proxy.newProxyInstance(Speakable.class.getClassLoader(),new Class[]{Speakable.class}, h); 26         s.speak(); 27         } 28  29 }

输出结果:

I'm log machine. Car moving... I'm log machine. Driver speak...

在汽车move()跟司机speak()之前,都自动实现LogHandler操作!

-----------------------------------------------------

后语:

通过上述例子,总结动态代理优点:

·适用任何类的任何方法;

·使用灵活,可随时将代理工具类加入或抽出。

动态代理是Java语言的精华所在,很多的开发框架都是基于其内部原理。本人目前对动态代理的理解也仅限于此,欢迎对Java有深度学识的朋友拍砖,谢谢~

正文到此结束
Loading...