在代理模式(一)裏面咱們瞭解了靜態代理、動態代理、CGLIB代理的基本使用。java
這篇主要來將JDK
動態代理底層的原理,以及有關$Proxy0、InvocationHandler
相關的原理。併發
TimeTankProxy
可是封裝在MyProxy
中只是將TankTimeProxy
封裝在了Proxy內部,咱們用動態編譯(JDK1.6 Complier)在程序運行的時候動態生成TankTimeProxy
類。框架
缺點: 只能對TankTimeProxy
進行代理dom
下面咱們初步模擬JDK
內部動態代理,由於動態代理就是不能看到代理類,因此咱們將代理類寫到MyProxy
內部,在程序運行的時候動態生成。分佈式
public interface Movable { void move(); } public class Tank implements Movable { @Override public void move() { // 坦克移動 System.out.println("Tank Moving......"); try { Thread.sleep(new Random().nextInt(5000)); // 隨機產生 1~5秒, 模擬坦克在移動 } catch (InterruptedException e) { e.printStackTrace(); } } }
下面看咱們很重要的MyProxy
類,它有一個靜態方法newProxyInstance()
能夠用來生成代理對象:ide
注意這裏 package proxy.advance.one
是我要動態編譯生成的那個代理類TankTimeProxy
最後生成所在的包。高併發
public class MyProxy { // 用來產生代理類 public static Object newProxyInstance() throws Exception{ String rt = "\n\r"; //動態代理文件的源碼 : 須要動態編譯代碼 String src = "package proxy.advance.one;" + rt + "public class TankTimeProxy implements Movable {" + rt + " private Movable tank;" + rt + " public TankTimeProxy(Movable tank) {" + rt + " this.tank = tank;" + rt + " }" + rt + " @Override" + rt + " public void move() {" + rt + " long start = System.currentTimeMillis();" + rt + " System.out.println(\"start time : \" + start);" + rt + " tank.move();" + rt + " long end = System.currentTimeMillis();" + rt + " System.out.println(\"end time : \" + end);" + rt + " System.out.println(\"spend all time : \" + (end - start)/1000 + \"s.\");" + rt + " }" + rt + "}"; //把源碼寫到java文件裏 File file = new File("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/proxy/advance/one/TankTimeProxy.java"); FileWriter fw = new FileWriter(file); fw.write(src); fw.flush(); fw.close(); //下面的代理,就是動態編譯 //編譯源碼,生成class,注意編譯環境要換成jdk纔有compiler,單純的jre沒有compiler,會空指針錯誤 JavaCompiler jc = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);//文件管事器 Iterable units = fileMgr.getJavaFileObjects(file); //編譯單元 JavaCompiler.CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);//編譯任務 t.call(); fileMgr.close(); //把類load到內存裏 並 生成新對象 !!!!!注意:下面的home前面不要加 / URL[] urls = new URL[]{new URL("file:/" + "home/zxzxin/Java_Maven/DesignPatterns/src/main/java/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("proxy.advance.one.TankTimeProxy"); //生成實例return c.newInstance(); //c.newInstance()會調用無參數的Constructor,若類沒有無參的Constructor時會出錯 Constructor ctr = c.getConstructor(Movable.class); // 能夠獲得帶有參數的構造方法() return ctr.newInstance(new Tank()); } }
測試:測試
public class Client { public static void main(String[] args) throws Exception { Movable tank = new Tank(); // 如今就是說刪除了TankTimeProxy,仍是要能實現動態代理 Movable tankProxy = (Movable) MyProxy.newProxyInstance(); // 動態代理不須要寫出代理類的名字 tankProxy.move(); } }
輸出: (和咱們使用JDK動態代理同樣的)this
start time : 1551318534681 Tank Moving...... end time : 1551318536469 spend all time : 1s.
咱們動態生成編譯的類也在當前包下:url
上面雖然實現了對JDK的動態代理的模擬,可是卻只能對Movable
接口進行代理,並且代理的邏輯只能是TimeProxy
,下面咱們來改進MyProxy
類:
這裏咱們裏面的那個字符串拼接代理類,不叫TankTimeProxy
了,暫且叫$MyProxy0
。
MyProxy
的newProxyInstance(Class inface)
;inface.getMethods()
取出全部方法,拼接實現方法的字符串(反射);
MyInvocationHandler
接口,並接收被代理的對象及方法做爲參數invoke(Object o, Method m);
;MyProxy
的靜態方法newProxyInstance(Class inface, MyInvocationHandler h)
;$MyProxy0
中,並且在每個代理方法內部,要調用被代理對象的原始方法,具體就是下面兩行:" Method md = " + inface.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt + //這個接口傳入了 ,注意必定要寫inface.getName " h.invoke(this, md);" + rt + 好比傳入Movable接口,裏面有move()方法,則上面生成的代碼是這樣: Method md = proxy.advance.two.Flyable.class.getMethod("fly"); h.invoke(this, md);
講的差很少了,如今看代碼實現:
代碼:
/** * 能處理任何方法的 調用 只要給我一個Method就能對這個方法進行特殊的處理 * 特殊處理的方式是由子類(實現類)決定 */ public interface MyInvocationHandler { void invoke(Object o, Method m); } 最重要的MyProxy類,傳入了兩個參數,分別是能夠指定任意接口,以及指定任意邏輯。 public class MyProxy { public static Object newProxyInstance(Class inface, MyInvocationHandler h) throws Exception { String rt = "\n\r"; String methodStr = ""; Method[] methods = inface.getMethods(); //獲取接口的全部方法 , 爲全部這些方法都生成代理 /* 原來固定的思路 : 只能對時間代理 for(Method m : methods) { methodStr += "@Override" + rt + "public void " + m.getName() + "() {" + rt + " long start = System.currentTimeMillis();" + rt + " System.out.println(\"start time : \" + start);" + rt + " t." + m.getName() + "();" + rt + " long end = System.currentTimeMillis();" + rt + " System.out.println("spend all time : " + (end - start)/1000 + "s.");" + rt + "}"; } */ for (Method m : methods) { methodStr += " @Override" + rt + " public void " + m.getName() + "(){" + rt + " try {" + rt + " Method md = " + inface.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt + //這個接口傳入了 ,注意必定要寫inface.getName " h.invoke(this, md);" + rt + " }catch(Exception e) {" + rt + " e.printStackTrace();" + rt + " }" + rt + " }"; } String src = "package proxy.advance.two;" + rt + "import java.lang.reflect.Method;" + rt + "public class My$Proxy0 implements " + inface.getName() + "{" + rt + " proxy.advance.two.MyInvocationHandler h;" + rt + //定義成員變量 MyInvocationHandler對象 " public My$Proxy0(MyInvocationHandler h) {" + rt + " this.h = h;" + rt + " }" + rt + methodStr + rt + "}"; //把源碼寫到java文件裏 File file = new File("/home/zxzxin/Java_Maven/DesignPatterns/src/main/java/proxy/advance/two/My$Proxy0.java"); FileWriter fw = new FileWriter(file); fw.write(src); fw.flush(); fw.close(); //下面的代理,就是動態編譯 //編譯源碼,生成class,注意編譯環境要換成jdk纔有compiler,單純的jre沒有compiler,會空指針錯誤 JavaCompiler jc = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = jc.getStandardFileManager(null, null, null);//文件管事器 Iterable units = fileMgr.getJavaFileObjects(file); //編譯單元 JavaCompiler.CompilationTask t = jc.getTask(null, fileMgr, null, null, null, units);//編譯任務 t.call(); fileMgr.close(); //把類load到內存裏 並 生成新對象 !!!!!注意:下面的home前面不要加 / URL[] urls = new URL[]{new URL("file:/" + "home/zxzxin/Java_Maven/DesignPatterns/src/main/java/")}; URLClassLoader ul = new URLClassLoader(urls); Class c = ul.loadClass("proxy.advance.two.My$Proxy0"); // System.out.println("Class c : " + c); // 這是以前的 // 生成實例return c.newInstance(); //c.newInstance()會調用無參數的Constructor,若類沒有無參的Constructor時會出錯 // Constructor ctr = c.getConstructor(Movable.class); // 能夠獲得帶有參數的構造方法() // return ctr.newInstance(new Tank()); Constructor ctr = c.getConstructor(MyInvocationHandler.class); // 哪一個處理器實現,就建立這個類的實例對象 Object m = ctr.newInstance(h); return m; } }
看一個指定時間邏輯的實現類:
public class MyTimeInvocationHandler implements MyInvocationHandler { private Object target; //注意是 Object,這樣能夠對任意對象進行時間的代理 public MyTimeInvocationHandler(Object target) { this.target = target; } @Override public void invoke(Object proxy, Method m) { // 在前面作一些事情: 記錄開始時間 long start = System.currentTimeMillis(); System.out.println("start time : " + start); System.out.println("proxy : " + proxy.getClass().getName()); // 打印proxy 究竟是什麼 System.out.println("target : " + target.getClass().getName()); // 打印 target 究竟是什麼 try { m.invoke(target); // 調用 target的方法 } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end time : " + end); System.out.println("spend all time : " + (end - start) / 1000 + "s."); } }
測試: (這裏我加了一個Flyable
和Plane
),測試能夠放入任意接口(這兩個類在代理模式(一)中也有,代碼很簡單,就不貼了):
// 能夠生成實現了任何接口的代理, 只要把接口傳進去就能夠了 public class Client { public static void main(String[] args) throws Exception { Movable tank = new Tank(); MyInvocationHandler timeHandler = new MyTimeInvocationHandler(tank); Movable tankProxy = (Movable) MyProxy.newProxyInstance(Movable.class, timeHandler); // 傳入類的.class便可 tankProxy.move(); System.out.println("--------------------"); Flyable plane = new Plane(); timeHandler = new MyTimeInvocationHandler(plane); Flyable planeProxy = (Flyable) MyProxy.newProxyInstance(Flyable.class, timeHandler); planeProxy.fly(); } }
輸出:
看咱們在包下生成的MyProxy0
類的內容:
如今再看這個總體的框架聯繫圖,應該就比較清晰了:
在上面的主程序測試類中,當調用tank.move()
的時候,就會調用invoke(this, md)
,而這個md
就是具體實現MyInvocationHandler
接口的MyTimeProxyInvocation
的方法, 也就是invoke()
(在這個方法中咱們在先後加了本身的邏輯)方法。
免費Java高級資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q