前言:java
代理模式做爲常見的設計模式之一,在項目開發中不可或缺。本文就嘗試着揭開代理的神祕面紗,也歡迎各路人批評指正!設計模式
【假設有個關於汽車移動(move)的計時需求】
設計:Moveable接口,一個Car的實現類;兩個代理CarTimer,TimeHandler.UML圖以下:框架
1)繼承dom
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 }
2)組合ide
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 }
客戶端代碼:工具
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
分析:從上述例子實現當中,咱們第一感受天然是分不出兩種代理的實現方式孰優孰劣。且繼續往下面看。this
【假設如今特殊需求不肯定:「汽車移動以前先往左仍是先往右」】
很明顯,咱們此時若使用繼承的方式實現代理,則後續很不容易維護,並且會造成臃腫的繼承鏈;但使用接口的方式咱們發現僅須要兩個代理類:TurnLeft,TurnRight。並且,無論後續需求如何都只須要作簡單的調整。UML圖以下:spa
----------設計
TurnLeft.java
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 }
TurnRight.java
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 }
客戶端代碼:
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...
========================
其實,無論是繼承仍是組合的方式,咱們上面實現的都僅僅是「靜態代理」,也是咱們平時用的比較多的。如今,咱們開始聊聊Java的神器---「動態代理」。
【假設如今須要實現一個「萬能」的日誌工具,即無論對任何類的任何方法均可以動態對其進行日誌操做】
例如:上面的例子中,請思考如何實如今汽車移動以前進行日誌操做?
常規的靜態代理方式固然能夠實現,下面咱們就利用Java中的Proxy類進行實現。UML圖以下:
添加關鍵類: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類是虛擬存在的,在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有深度學識的朋友拍磚,謝謝~