上一篇文章我們講了靜態代理的實現方式,并比較了聚合實現靜態代理和繼承實現效果的不同。今天我們來逐步實現動態代理,并模仿 JDK 動態代理的實現。
-
什么是動態代理?
我們首先考慮,之前我們是通過編寫新的類實現與被代理對象同樣的接口最終來達到目的的,這樣對于每一個被代理對象,我們都要寫一個新的代理類,這樣的方式肯定不好,如果有這樣一個類,只要我們傳進去被代理對象,它能自動幫助我們生成代理對象,那不就好了嗎? Client 客戶端的要求如下:
Tank tank = new Tank() ; Moveable tankTimeProxy = (Moveable)Proxy.newInstance(tank) ;
-
動態代理所需要的步驟:
- 既然要求自動幫助我們生成被代理對象,必須要解決的是:生成被代理類,編譯被代理類,將該類 load 到內存中,生成代理對象。所以我們先以上一篇的時間代理為例子,來解決這個問題:
public class Proxy { public static Moveable newInstance(Moveable m) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 要生成的代理類,以字符串的形式寫出 String str = "package dynamicProxy ;" + "public class TankTimeProxy implements Moveable {\n" + " public TankTimeProxy(Moveable m) {\n" + " this.m = m;\n" + " }\n" + "\n" + " private Moveable m ;\n" + "\n" + " @Override\n" + " public void move() throws InterruptedException {\n" + " long startTime = System.currentTimeMillis() ;\n" + " m.move();\n" + " long endTime = System.currentTimeMillis() ;\n" + " System.out.println(\"time : \" + (endTime - startTime));\n" + " }\n" + "}\n" ; // 將定義好的被代理類寫入文件中 // System.getProperty("user.dir") 為獲取當前項目的路徑 String fileName = System.getProperty("user.dir") + "/src/dynamicProxy/TankTimeProxy.java" ; File file = new File(fileName) ; FileWriter fw = new FileWriter(file) ; fw.write(str); fw.flush(); fw.close(); // 編譯被代理類,這里的實現是才用的 JDK6 之后的版本所提供的編譯器 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler() ; // 該編譯器的代碼需要交由 StandardJavaFileManager 來管理 StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null,null,null) ; // 獲取所要編譯的單元 Iterable units = standardJavaFileManager.getJavaFileObjects(fileName) ; // 獲取編譯任務 JavaCompiler.CompilationTask task = javaCompiler.getTask(null,standardJavaFileManager,null,null,null,units) ; // 執行編譯任務 task.call() ; standardJavaFileManager.close(); // 將編譯好的代理類 load 到內存中并創建代理對象 // 獲取我們編譯好的 class 文件路徑 URL[] urls = new URL[]{new URL("file:/" + System.getProperty("user.dir") + "/src/")} ; // 獲取 classLoader URLClassLoader ucl = new URLClassLoader(urls) ; // laod 到內存中 Class c = ucl.loadClass("dynamicProxy.TankTimeProxy") ; // 獲取參數為 Moveable 的構造器 Constructor constructor = c.getConstructor(Moveable.class) ; // 實例化代理對象 Moveable proxy = (Moveable) constructor.newInstance(new Tank()); return proxy ; } }
通過以上代碼,我們就解決了系統生成代理對象所需要的步驟。那么問題來了,現在我們只能實現靜態的代理,如何實現動態代理呢?
- 接著我們先來進行動態代理不同的接口的實現。這里要考慮的問題應該是,對于被代理的對象,我們首先要獲取其所有的方法,并對其所有的方法實現代理,所以就要修改一部分代理類的代碼了。
- 首先是方法體的定義,應該改為接收被代理對象的接口類,即改:
為 :public static Moveable newInstance(Moveable m)
public static Object newInstance(Class inface)
- 獲取該接口中所有的方法:(這里涉及到 java 的反射機制)
Method[] methods = Method[] methods = inface.getMethods() ;
- 拼接代理類代碼的方法字符串:
這里也許很多人會有疑問,String methodStr ="" ; for(Method m : methods){ methodStr += " @Override\n" + " public void " + m.getName() + "() {\n" + " try{\n" + " Method md = " + inface.getName() + ".class.getMethod(\""+ m.getName() +"\");\n" + " handler.invoke(this,md) ;\n" + " } catch (Exception e){\n" + " e.printStackTrace() ;\n" + " }\n" + " }" ; }
Method md = " + inface.getName() + ".class.getMethod(\""+ m.getName() +"\");\n"
這里為什么不能用Method md = m
來代替呢?其實這里涉及到內外層代碼的嵌套問題。 methodStr 是為了生成代理類的字符串,其包含的 method 是內層代碼,要獲取其方法,就要通過傳進去的接口及方法名來獲取,而 m 是我們的生成代碼,是外層代碼。詳細的區別,大家可以具體去代碼里面嘗試一下,看看生成的代理類的區別就應該可以看出來了。- 修改生成代碼 str ,加入方法字符串,并修改實現的接口:
String str = "package dynamicProxy ;\n" + "import java.lang.reflect.Method; \n" + "public class TankTimeProxy implements " + inface.getName() + " {\n" + " public TankTimeProxy(Moveable m) {\n" + " this.m = m;\n" + " }\n" + "\n" + " private " + inface.getName + " m ;\n" + "\n" + methodStr + "}\n" ;
- 修改構造器的參數和返回的對象:
Constructor constructor = c.getConstructor(inface) ; Object proxy = constructor.newInstance(new Tank());
到了這里,我們就完成了能對所有的接口的所有方法進行代理。但還有一個問題,就是代理的邏輯已經被寫死了,所以下一步我們要完成的就是實現能夠自定義代理邏輯的動態代理了。