在代理模式(一)裏面咱們瞭解了靜態代理、動態代理、CGLIB代理的基本使用。java
這篇主要來將JDK
動態代理底層的原理,以及有關$Proxy0、InvocationHandler
相關的原理。bash
只是將TankTimeProxy
封裝在了Proxy內部,咱們用動態編譯(JDK1.6 Complier)在程序運行的時候動態生成TankTimeProxy
類。框架
缺點: 只能對TankTimeProxy
進行代理dom
下面咱們初步模擬JDK
內部動態代理,由於動態代理就是不能看到代理類,因此咱們將代理類寫到MyProxy
內部,在程序運行的時候動態生成。ide
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()
能夠用來生成代理對象:測試
注意這裏 package proxy.advance.one
是我要動態編譯生成的那個代理類TankTimeProxy
最後生成所在的包。ui
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());
}
}複製代碼
測試:this
public class Client {
public static void main(String[] args) throws Exception {
Movable tank = new Tank();
// 如今就是說刪除了TankTimeProxy,仍是要能實現動態代理
Movable tankProxy = (Movable) MyProxy.newProxyInstance(); // 動態代理不須要寫出代理類的名字
tankProxy.move();
}
}複製代碼
輸出: (和咱們使用JDK動態代理同樣的)url
start time : 1551318534681
Tank Moving......
end time : 1551318536469
spend all time : 1s.複製代碼
咱們動態生成編譯的類也在當前包下:spa
上面雖然實現了對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()
(在這個方法中咱們在先後加了本身的邏輯)方法。