動(dòng)態(tài)代理

需要知道一個(gè)東西是怎么來(lái)的完慧,你做一個(gè)就清楚了,所以我著手模擬了下

首先定義一個(gè)接口
public interface Action {
    public void move();
    public void speak();
}
實(shí)現(xiàn)這個(gè)接口
/**
 * @Author: YangZaining
 * @Date: Created in 19:40$ 2018/8/11$
 */
public class ActionImpl implements Action{
    @Override
    public void move() {
        System.out.println("移動(dòng)了件炉。市殷。愕撰。。");
    }

    @Override
    public void speak() {
        System.out.println("sxl nb");//別在意這句話,我好朋友而已
    }
}

現(xiàn)在我們有這樣一個(gè)要求搞挣,對(duì)move方法進(jìn)行測(cè)試計(jì)算它的執(zhí)行時(shí)間带迟,則我們會(huì)想到繼承或組合這個(gè)類,或者直接在main方法里調(diào)用測(cè)試囱桨,以下只是實(shí)現(xiàn)它的一種方式
如下:

/**
 * @Author: YangZaining
 * @Date: Created in 19:59$ 2018/8/11$
 */
public class Test implements Action {

    Action action;

    public Test(Action action) {//傳入實(shí)現(xiàn)的子類即可
        this.action = action;
    }

    @Override
    public void move() {
        long st = System.currentTimeMillis();
        action.move();
        long ed = System.currentTimeMillis();
        System.out.println("運(yùn)行了" + (ed - st) + "ms");
    }

    @Override
    public void speak() {
        action.speak();
    }
    public static void main(String[] args) {
        Action action = new ActionImpl();
        new Test(action).move();
    }
}
output:
移動(dòng)了仓犬。。舍肠。搀继。
運(yùn)行了199840us

現(xiàn)在我又有個(gè)要求,在speak方法前后加入一句話,這時(shí)候你又會(huì)想到簡(jiǎn)單貌夕,再寫(xiě)這樣一個(gè)類就可以了律歼,但是每次需求的變更你都要寫(xiě)這樣一個(gè)類,不是很難受嗎啡专?而且我們需要測(cè)試的時(shí)候才記錄這些內(nèi)容,上線了制圈,我們要撤銷這些內(nèi)容们童,這樣做的話不是很麻煩嗎?
所以我們需要一個(gè)代理的代理鲸鹦,去減輕工作量慧库,幫我們生成這樣的代理對(duì)象,所以我們要著手動(dòng)態(tài)的去創(chuàng)建這個(gè)類

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @Author: YangZaining
 * @Date: Created in 19:32$ 2018/8/11$
 */
public class Proxy {
    public static Object newProxynewInstance() throws Exception {
        String s = "package cn.learn;\n" +
                "\npublic class Test implements Action {\n" +
                "\n" +
                "    Action action;\n" +
                "\n" +
                "    public Test(Action action) {\n" +
                "        this.action = action;\n" +
                "    }\n" +
                "\n" +
                "    @Override\n" +
                "    public void move() {\n" +
                "        long st = System.nanoTime();\n" +
                "        action.move();\n" +
                "        long ed = System.nanoTime();\n" +
                "        System.out.println(\"運(yùn)行了\" + (ed - st) + \"us\");\n" +
                "    }\n" +
                "\n" +
                "    @Override\n" +
                "    public void speak() {\n" +
                "        action.speak();\n" +
                "    }\n" +
                "\n" +
                "}\n";
        String filePath =  "D:\\src\\cn\\learn\\Test.java";//本地建一個(gè)目錄馋嗜,以免和編譯期編譯的文件混淆
        File fs = new File(filePath);
        FileWriter fw = new FileWriter(fs);//寫(xiě)入文件里
        fw.write(s);
        fw.flush();
        fw.close();
        //編譯文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println("CompilerName:" + compiler);//編譯器名字
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable it = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null,it);
        t.call();
        fileManager.close();
        //加載到內(nèi)存并且生成新對(duì)象
        URL[] urls = new URL[]{new URL("file:/"+"d:/src/")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class c = urlClassLoader.loadClass("cn.learn.Test");
        Constructor constructor = c.getConstructor(Action.class);
        Object object =  constructor.newInstance(new ActionImpl());
        return object;
    }
}

然后主函數(shù)里調(diào)用這個(gè)方法既可以獲取到代理對(duì)象齐板,對(duì)這個(gè)類繼續(xù)加工

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @Author: YangZaining
 * @Date: Created in 19:32$ 2018/8/11$
 */
public class Proxy {
    public static Object newProxynewInstance(Class inf) throws Exception {
        Method[] methods = inf.getMethods();
        String s = "package cn.learn;\n" +
                "\npublic class Test implements " + inf.getName() + " {\n" +
                "\n" +
                "    " + inf.getName() + " action;\n" +
                "\n" +
                "    public Test(" + inf.getName() + " action) {\n" +
                "        this.action = action;\n" +
                "    }\n";
        String ln = "\r\n";
        for (Method m : methods) {
            s += "    @Override\n";
            if ("move".equals(m.getName())) {
                s += "public " + m.getGenericReturnType() + " " + m.getName() + "(){" +
                        "        long st = System.nanoTime();\n" +
                        "        action."+m.getName()+"();\n" +
                        "        long ed = System.nanoTime();\n" +
                        "        System.out.println(\"運(yùn)行了\" + (ed - st) + \"us\");\n" +
                        "}";
            }
            else  {
                s += "public " + m.getGenericReturnType() + " " + m.getName() + "(){" +
                        "        action."+m.getName()+"();\n" +
                        "    }\n" +
                        "\n";
            }
        }
        s+="}\n";
        String filePath =  "D:\\src\\cn\\learn\\Test.java";
        File fs = new File(filePath);
        FileWriter fw = new FileWriter(fs);//寫(xiě)入文件里
        fw.write(s);
        fw.flush();
        fw.close();
        //編譯文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println("CompilerName:" + compiler);//編譯器名字
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable it = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, it);
        t.call();
        fileManager.close();
        //加載到內(nèi)存并且生成新對(duì)象
        URL[] urls = new URL[]{new URL("file:/"+"d:/src/")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class c = urlClassLoader.loadClass("cn.learn.Test");
        Constructor constructor = c.getConstructor(inf);
        Object object = constructor.newInstance(new ActionImpl());
        return object;
    }
}

進(jìn)一步加工,我們會(huì)發(fā)現(xiàn)這個(gè)只能實(shí)現(xiàn)測(cè)試時(shí)間的一個(gè)代理葛菇,而且還只是在move方法上
所以我們要定義一個(gè)專門(mén)處理事件的接口

import java.lang.reflect.Method;

public interface InvocationHandler {
    public void invoke(Object o,Method m);//需要執(zhí)行的方法對(duì)象和方法本身(我們定義的函數(shù)沒(méi)有參數(shù)甘磨,暫且先不定義)
}

現(xiàn)在實(shí)現(xiàn)這個(gè)接口

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: YangZaining
 * @Date: Created in 23:26$ 2018/8/11$
 */
public class TalkHandler implements InvocationHandler{
    @Override
    public void invoke(Object o,Method m) {
        System.out.println("我前進(jìn)了一步");
        try {
            m.invoke(o,new Object[]{});
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("我后退了一步");
    }
}

為實(shí)現(xiàn)方便,就不特定判斷哪個(gè)方法是move,哪個(gè)方法是speak了眯停,下面是改進(jìn)后的代碼,這一塊做了幾個(gè)變動(dòng):1)生成代理對(duì)象構(gòu)造函數(shù)的參數(shù)改變济舆;2)在生成代碼塊里增加了Method方法傳值;3)以及下面構(gòu)造器傳值莺债;
為什么要怎么做呢滋觉?因?yàn)槲覀冃枰屛覀兩傻拇韺?duì)象按照我們所規(guī)定的方法進(jìn)行執(zhí)行,所以我們需要把我們的事件(InvocationHandler )進(jìn)行傳遞,而我們構(gòu)造的事件執(zhí)行需要被代理對(duì)象(Action)的方法齐邦,所以我們生成的代理對(duì)象(Test,名字無(wú)所謂只是對(duì)應(yīng)本例是這樣的)中反射進(jìn)行傳值椎侠,這一塊算是比較饒的地方,這里代碼語(yǔ)義容易混淆措拇,應(yīng)對(duì)照生成的代理對(duì)象理解,實(shí)在不明白就自己嘗試著修改我纪。

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * @Author: YangZaining
 * @Date: Created in 19:32$ 2018/8/11$
 */
public class Proxy {
    public static Object newProxynewInstance(Class inf, InvocationHandler in) throws Exception {
        Method[] methods = inf.getMethods();
        String ln = "\r\n";
        String s = "package cn.learn;" + ln +
                "public class Test implements " + inf.getName() + " {" + ln +
                ln +
                "    InvocationHandler in;" + ln +
                "    public Test(InvocationHandler in) {" + ln +//事件傳入
                "        this.in = in;" + ln +
                "    }\n";
        for (Method m : methods) {
            s += "    @Override" + ln +
                    "public " + m.getGenericReturnType() + " " + m.getName() + "() {" + ln +
                    "try{"+ln+
                    " java.lang.reflect.Method ms = " + inf.getName() + ".class.getMethod(\"" + m.getName() + "\");" + ln +
                    "in.invoke(this,ms);" + ln +//事件方法的執(zhí)行
                    "}catch(Exception e){"+ln+
                    "e.printStackTrace();"+ln+
                    "}"+ln+
                    "    }" + ln + ln;
        }
        s += "}" + ln;
        String filePath = "D:\\src\\cn\\learn\\Test.java";
        File fs = new File(filePath);
        FileWriter fw = new FileWriter(fs);//寫(xiě)入文件里
        fw.write(s);
        fw.flush();
        fw.close();
        //編譯文件
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.out.println("CompilerName:" + compiler);//編譯器名字
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable it = fileManager.getJavaFileObjects(filePath);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, it);
        t.call();
        fileManager.close();
        //加載到內(nèi)存并且生成新對(duì)象
        URL[] urls = new URL[]{new URL("file:/" + "d:/src/")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class c = urlClassLoader.loadClass("cn.learn.Test");
        System.out.println(c);
        Constructor constructor = c.getConstructor(InvocationHandler.class);//發(fā)生改變,因?yàn)樯傻拇韺?duì)象傳值改變了
        Object object = constructor.newInstance(in);//給生成的代理對(duì)象傳值
        return object;
    }
}

生成的代理對(duì)象文件內(nèi)容

package cn.learn;

public class Test implements cn.learn.Action {

    InvocationHandler in;

    public Test(InvocationHandler in) {
        this.in = in;
    }

    @Override
    public void speak() {
        try {
            java.lang.reflect.Method ms = cn.learn.Action.class.getMethod("speak");
            in.invoke(this, ms);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void move() {
        try {
            java.lang.reflect.Method ms = cn.learn.Action.class.getMethod("move");
            in.invoke(this, ms);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

這樣還不算完,這時(shí)我們執(zhí)行會(huì)陷入死循環(huán)



這里原因很簡(jiǎn)單宣羊,因?yàn)樯傻拇韺?duì)象我們把它自己傳給了InvocationHandler 執(zhí)行了其中的invoke方法璧诵,然后執(zhí)行的對(duì)象是生成的代理對(duì)象,然后又自己調(diào)用了自己的方法仇冯。
以move方法為例
首先
Test in.invoke(this,ms) --> TalkHandler void invoke(Object o,Method m)
由于this和Object o 是同一對(duì)象所以當(dāng)執(zhí)行TalkHandler m.invoke(o,new Object[]{});時(shí)相當(dāng)于調(diào)用了 Test中的public void speak() 方法之宿,然后speak方法里又重新來(lái)一次,就陷入了死循環(huán)苛坚。
所以做出如下修改

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: YangZaining
 * @Date: Created in 23:26$ 2018/8/11$
 */
public class TalkHandler implements InvocationHandler {
    private Object obj;

    public TalkHandler(Object obj) {//傳入所需要代理的對(duì)象
        this.obj = obj;
    }

    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
    }

    @Override
    public void invoke(Object o, Method m) {
        System.out.println("我前進(jìn)了一步");
        try {
            m.invoke(obj, new Object[]{});//調(diào)用obj
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        System.out.println("我后退了一步");
    }
}

然后進(jìn)行測(cè)試

/**
 * @Author: YangZaining
 * @Date: Created in 20:29$ 2018/8/11$
 */
public class Main {
    public static void main(String[] args) {
        try {
         Action action = (Action) Proxy.newProxynewInstance(Action.class,new TalkHandler(new ActionImpl()));
         action.move();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
output:
CompilerName:com.sun.tools.javac.api.JavacTool@85ede7b
class cn.learn.Test
我前進(jìn)了一步
移動(dòng)了比被。。泼舱。等缀。
我后退了一步

JAVA API中的Proxy和InvocationHandler實(shí)現(xiàn)了更多細(xì)節(jié),還需要閱讀文檔


我們能發(fā)現(xiàn)一個(gè)問(wèn)題既然InvocationHandler中的Object o 沒(méi)有用到娇昙,是不是可以去掉呢尺迂,答案是顯然的,只是當(dāng)我們要取生成代理類的某些方法或?qū)傩詴r(shí)冒掌,需要用到而已

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末噪裕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子股毫,更是在濱河造成了極大的恐慌膳音,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铃诬,死亡現(xiàn)場(chǎng)離奇詭異祭陷,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)趣席,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)兵志,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吩坝,你說(shuō)我怎么就攤上這事毒姨。” “怎么了钉寝?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵弧呐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嵌纲,道長(zhǎng)俘枫,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任逮走,我火速辦了婚禮鸠蚪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己茅信,他們只是感情好盾舌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著蘸鲸,像睡著了一般妖谴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酌摇,一...
    開(kāi)封第一講書(shū)人閱讀 51,727評(píng)論 1 305
  • 那天膝舅,我揣著相機(jī)與錄音,去河邊找鬼窑多。 笑死仍稀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埂息。 我是一名探鬼主播技潘,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耿芹!你這毒婦竟也來(lái)了崭篡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吧秕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后迹炼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體砸彬,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年斯入,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砂碉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刻两,死狀恐怖增蹭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情磅摹,我是刑警寧澤滋迈,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站户誓,受9級(jí)特大地震影響饼灿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帝美,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一碍彭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦庇忌、人聲如沸舞箍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疏橄。三九已至,卻和暖如春墙基,著一層夾襖步出監(jiān)牢的瞬間软族,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工残制, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留立砸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓初茶,卻偏偏與公主長(zhǎng)得像颗祝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恼布,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容