深刻理解代理模式

1、目的

在代理模式(一)裏面咱們瞭解了靜態代理、動態代理、CGLIB代理的基本使用。java

這篇主要來將JDK動態代理底層的原理,以及有關$Proxy0、InvocationHandler相關的原理。bash

2、模擬Java底層動態代理實現

一、模擬TimeTankProxy可是封裝在MyProxy中

只是將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

  • 首先,要實現對任意接口的任意方法代理:
    • 實現代理任意接口: 咱們要把接口類型做爲參數傳給MyProxynewProxyInstance(Class inface)
    • 代理接口的全部方法: 用inface.getMethods()取出全部方法,拼接實現方法的字符串(反射);


  • 要實現代理的任意邏輯:
    • 要把代理邏輯抽離,應獨立出一個策略接口: MyInvocationHandler接口,並接收被代理的對象及方法做爲參數invoke(Object o, Method m);
    • 並且須要把自己做爲參數傳給MyProxy的靜態方法newProxyInstance(Class inface, MyInvocationHandler h)
    • 並且咱們具體的策略(即MyInvocationHandler的實現類)自己聚合被代理類target,以便在target的方法先後增長代理邏輯;
    • 並且其中很重要的一步: 咱們須要把MyInvocationHandler做爲成員遍歷放到$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.");
    }
}複製代碼

測試: (這裏我加了一個FlyablePlane),測試能夠放入任意接口(這兩個類在代理模式(一)中也有,代碼很簡單,就不貼了):

// 能夠生成實現了任何接口的代理, 只要把接口傳進去就能夠了
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類的內容:



如今再看這個總體的框架聯繫圖,應該就比較清晰了:

3、總結

在上面的主程序測試類中,當調用tank.move()的時候,就會調用invoke(this, md),而這個md就是具體實現MyInvocationHandler接口的MyTimeProxyInvocation的方法, 也就是invoke()(在這個方法中咱們在先後加了本身的邏輯)方法。

相關文章
相關標籤/搜索