一、動態代理的使用
動態代理可以提供對另一個對象的訪問,同時隱藏實際對象的具體事實。代理一般會實現它所表示的實際對象的接口。代理可以訪問實際對象,但是延遲實現實際對象的部分功能,實際對象實現系統的實際功能,代理對象對客戶隱藏了實際對象。客戶不知道它是與代理打交道還是與實際對象打交道。
動態代理主要包含以下角色:
動態代理類(以下簡稱為代理類)是一個實現在創建類時在運行時指定的接口列表的類,該類具有下面描述的行為。
代理接口 是代理類實現的一個接口。
代理實例 是代理類的一個實例。
每個代理實例都有一個關聯的調用處理程序 對象,它可以實現接口 InvocationHandler
。通過其中一個代理接口的代理實例上的方法調用將被指派到實例的調用處理程序的 Invoke
方法,並傳遞代理實例、識別調用方法的 java.lang.reflect.Method
對象以及包含參數的 Object
類型的數組。調用處理程序以適當的方式處理編碼的方法調用,並且它返回的結果將作為代理實例上方法調用的結果返回。
目前Java開發包中包含了對動態代理的支持,但是其實現只支持對接口的的實現。 其實現主要通過java.lang.reflect.Proxy類和java.lang.reflect.InvocationHandler接口。 Proxy類主要用來獲取動態代理對象,InvocationHandler接口用來約束調用者實現
下面看一個例子:
1.代理接口:IComputer.java
1 package com.proxy; 2 3 public interface IComputer { 4 void execute(); 5 }
2.被代理對象:Laptop.java
1 package com.proxy; 2 3 //筆記本電腦 4 public class Laptop implements IComputer { 5 6 public void execute() { 7 System.out.println("電腦正在執行中......"); 8 } 9 10 }
3.調用處理類:TimeHander.java
1 package com.proxy; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 public class TimeHander implements InvocationHandler { 7 private Object object; 8 public TimeHander(Object object) { 9 this.object = object; 10 } 11 public Object invoke(Object proxy, Method method, Object[] args) 12 throws Throwable { 13 long start = System.currentTimeMillis(); 14 System.out.println("start:"+start); 15 method.invoke(object, args); 16 Thread.sleep((int)(Math.random()*2000)); 17 long end = System.currentTimeMillis(); 18 System.out.println("end:"+end); 19 System.out.println("total:"+(end-start)); 20 return null; 21 } 22 23 }
4.測試程序:
1 package com.proxy; 2 3 import java.lang.reflect.Proxy; 4 5 public class ProxyTest { 6 7 public static void main(String[] args) { 8 Laptop laptop = new Laptop(); 9 TimeHander hander = new TimeHander(laptop); 10 IComputer computer = (IComputer)Proxy.newProxyInstance(laptop.getClass().getClassLoader(), laptop.getClass().getInterfaces(), hander); 11 computer.execute(); 12 } 13 14 }
程序運行結果:
start:1369118281186
電腦正在執行中......
end:1369118282849
total:1663
二、動態代理運行機制
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法會返回一個代理對象類的實例。如上面例子中IComputer computer = (IComputer)Proxy.newProxyInstance(laptop.getClass().getClassLoader(), laptop.getClass().getInterfaces(), hander);執行這一句話的時候會通過反射機制動態的生成一個代理類,該類實現了IComputer接口,並且重寫了接口里面的方法(也就是說代理類與被代理類有相同的接口),在該代理類里面有一個InvocationHandler類型的成員變量,也就是調用處理程序,通過調用處理程序來給被代理類增強功能。創建好代理類后就調用類加載器將該類加載到類存,然后再通過反射創建一個該代理類的實例對象。
為了能夠更加的理解動態代理的運行機制,我自己來實現了一個動態代理:
1.代理接口:Moveable.java
1 package com.test; 2 3 public interface Moveable { 4 void move(); 5 }
2.被代理對象:Tank.java
1 package com.test; 2 3 import java.util.Random; 4 5 public class Tank implements Moveable { 6 7 public void move() { 8 System.out.println("Tank moving..."); 9 try { 10 Thread.sleep(new Random().nextInt(10000)); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 16 }
3.下面寫一個Proxy類來為被代理對象產生一個代理類對象,來實現增加記錄運行時間的功能。
1 package com.test; 2 3 import java.io.File; 4 import java.io.FileWriter; 5 import java.lang.reflect.Constructor; 6 import java.lang.reflect.Method; 7 8 import javax.tools.JavaCompiler; 9 import javax.tools.StandardJavaFileManager; 10 import javax.tools.ToolProvider; 11 import javax.tools.JavaCompiler.CompilationTask; 12 13 public class Proxy { 14 public static Object newProxyInstance(Class interfaces,InvocationHandler h)throws Exception{ 15 StringBuffer methodStr = new StringBuffer(); 16 String tr = "\r\n"; 17 Method[] methods = interfaces.getMethods(); 18 //拼接代理類的方法 19 for (Method method : methods) { 20 methodStr.append( 21 " public "+ method.getReturnType()+ " " +method.getName()+"() {" + tr + 22 " try {" + tr + 23 " java.lang.reflect.Method md = " + interfaces.getName() + "." + "class.getMethod(\"" + method.getName() + "\");" + tr + 24 " h.invoke(this,md);" + tr + 25 " }catch(Exception e) {e.printStackTrace();}" + tr + 26 " }" + tr 27 ); 28 } 29 30 //拼接代理類 31 String src = "package com.test;" + tr + 32 "import com.test.Moveable;" + tr + 33 "public class TimeProxy implements " + interfaces.getName() + " {" + tr + 34 " private com.test.InvocationHandler h;" + tr + 35 " public TimeProxy(com.test.InvocationHandler h) {" + tr + 36 " this.h = h;" + tr + 37 " }" + tr + 38 methodStr.toString() + tr + 39 "}"; 40 //創建代理類 41 String fileName = System.getProperty("user.dir") + "/src/com/test/TimeProxy.java"; 42 File file = new File(fileName); 43 FileWriter writer = new FileWriter(file); 44 writer.write(src); 45 writer.flush(); 46 writer.close(); 47 //編譯 48 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 49 StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); 50 Iterable units = fileMgr.getJavaFileObjects(fileName); 51 CompilationTask ct = compiler.getTask(null, fileMgr, null, null, null, units); 52 ct.call(); 53 fileMgr.close(); 54 //加載類到內存: 55 Class c = ClassLoader.getSystemClassLoader().loadClass("com.test.TimeProxy"); 56 Constructor constructor = c.getConstructor(InvocationHandler.class); //得到參數為InvocationHandler類型的構造方法 57 Object m = constructor.newInstance(h); //通過該構造方法得到實例 58 return m; 59 60 } 61 }
4.TankProxy.java
1 package com.test; 2 3 import java.lang.reflect.Method; 4 5 public class TankProxy { 6 public static <T> T getBean(final Object tank) throws Exception{ 7 return (T)Proxy.newProxyInstance(tank.getClass().getInterfaces()[0], new InvocationHandler(){ 8 public void invoke(Object proxy, Method method) { 9 long start = System.currentTimeMillis(); 10 System.out.println("start:"+start); 11 try { 12 method.invoke(tank, new Object[]{}); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } 16 long end = System.currentTimeMillis(); 17 System.out.println("end:"+end); 18 System.out.println("time:"+(end-start)); 19 } 20 21 }); 22 } 23 }
5.測試程序:
1 package com.test; 2 3 import java.util.List; 4 5 import com.extend.Tank2; 6 import com.extend.Tank3; 7 import com.juhe.LogProxy; 8 import com.juhe.TimeProxy; 9 10 public class Test { 11 public static void main(String[] args) throws Exception { 12 Tank tank = new Tank(); 13 Moveable m = TankProxy.getBean(tank); 14 m.move(); 15 16 } 17 18 }
執行該程序的結果為:
start:1369121253400
Tank moving...
end:1369121260078
time:6678
動態生成的代理類的內容如下:
1 package com.test; 2 import com.test.Moveable; 3 public class TimeProxy implements com.test.Moveable { 4 private com.test.InvocationHandler h; 5 public TimeProxy(com.test.InvocationHandler h) { 6 this.h = h; 7 } 8 public void move() { 9 try { 10 java.lang.reflect.Method md = com.test.Moveable.class.getMethod("move"); 11 h.invoke(this,md); 12 }catch(Exception e) {e.printStackTrace();} 13 } 14 15 }
看了這個例子,對動態代理的實現機制應該會有一定的了解了!
小結:動態代理在運行期通過接口動態生成代理類,這為其帶來了一定的靈活性,但這個靈活性卻帶來了兩個問題,第一代理類必須實現一個接口,如果沒實現接口會拋出一個異常。第二性能影響,因為動態代理使用反射的機制實現的,首先反射肯定比直接調用要慢,其次使用反射大量生成類文件可能引起Full GC造成性能影響,因為字節碼文件加載后會存放在JVM運行時區的方法區(或者叫持久代)中,當方法區滿的時候,會引起Full GC,所以當你大量使用動態代理時,可以將持久代設置大一些,減少Full GC次數。