設(shè)計(jì)模式之動(dòng)態(tài)代理 - 徹底搞懂JDK動(dòng)態(tài)代理

目錄

1.什么是JDK動(dòng)態(tài)代理
2.簡(jiǎn)單案例
3.徹底搞懂JDK動(dòng)態(tài)代理儡嘶,自己動(dòng)手實(shí)現(xiàn)JDK動(dòng)態(tài)代理蹦狂。
4.項(xiàng)目源碼

1.什么是JDK動(dòng)態(tài)代理

JDK動(dòng)態(tài)代理是設(shè)計(jì)模式中代理模式的一種實(shí)現(xiàn)方式,在java的運(yùn)行過(guò)程中窜骄,動(dòng)態(tài)的生成代理類(lèi)邻遏,其只能代理接口准验。

2.簡(jiǎn)單案例

本案例說(shuō)明
(1)創(chuàng)建一個(gè)抽象類(lèi),Person接口糊饱,使其擁有一個(gè)沒(méi)有返回值的doSomething方法另锋。
/**
 * 抽象類(lèi)人
 */
public interface Person {
    void doSomething();
}
(2)創(chuàng)建一個(gè)名為Bob的Person接口的實(shí)現(xiàn)類(lèi)夭坪,使其實(shí)現(xiàn)doSomething方法唉铜。
/**
 * 創(chuàng)建一個(gè)名為Bob的人的實(shí)現(xiàn)類(lèi)
 */
public class Bob implements Person {
    public void doSomething() {
        System.out.println("Bob doing something!");
    }
}
(3)創(chuàng)建JDK動(dòng)態(tài)代理類(lèi)竞惋,使其實(shí)現(xiàn)InvocationHandler接口。擁有一個(gè)名為target的變量拆宛,并創(chuàng)建getTarget獲取代理對(duì)象方法浑厚。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK動(dòng)態(tài)代理
 * 需實(shí)現(xiàn)InvocationHandler接口
 */
public class JDKDynamicProxy implements InvocationHandler {

    // 被代理的對(duì)象
    Person target;

    // JDKDynamicProxy構(gòu)造函數(shù)
    public JDKDynamicProxy(Person person) {
        this.target = person;
    }

    // 獲取代理對(duì)象
    public Person getTarget() {
        return (Person) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // 動(dòng)態(tài)代理invoke方法
    public Person invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 被代理方法前執(zhí)行
        System.out.println("JDKDynamicProxy do something before!");
        // 執(zhí)行被代理的方法
        Person result = (Person) method.invoke(target, args);
        // 被代理方法后執(zhí)行
        System.out.println("JDKDynamicProxy do something after!");
        return result;
    }
}
(4)創(chuàng)建JDK動(dòng)態(tài)代理測(cè)試類(lèi)JDKDynamicTest。
/**
 * JDK動(dòng)態(tài)代理測(cè)試
 */
public class JDKDynamicTest {

    public static void main(String[] args) {

        System.out.println("不使用代理類(lèi),調(diào)用doSomething方法炎滞。");
        // 不使用代理類(lèi)
        Person person = new Bob();
        // 調(diào)用doSomething方法
        person.doSomething();

        System.out.println("-------------------------------------- 分割線 --------------------------------------");

        System.out.println("使用代理類(lèi),調(diào)用doSomething方法册赛。");
        // 獲取代理類(lèi)
        Person proxyPerson = new JDKDynamicProxy(new Bob()).getTarget();
        // 調(diào)用doSomething方法
        proxyPerson.doSomething();

    }
}

運(yùn)行結(jié)果如下圖所示:


image.png

3.徹底搞懂JDK動(dòng)態(tài)代理,自己動(dòng)手實(shí)現(xiàn)JDK動(dòng)態(tài)代理票堵。

想要自己實(shí)現(xiàn)JDK動(dòng)態(tài)代理悴势,首先我們先梳理下原理:

1.拿到被代理對(duì)象的引用瞳浦,然后獲取其接口叫潦。
2.JDK代理重新生成一個(gè)類(lèi),同時(shí)實(shí)現(xiàn)我們給的代理所實(shí)現(xiàn)的接口矗蕊。
3.同時(shí)拿到代理對(duì)象的引用傻咖。
4.動(dòng)態(tài)生成class文件字節(jié)碼卿操。
5.編譯源代碼害淤,并生成. class文件窥摄。
6.將class文件動(dòng)態(tài)加載到JVM础淤。
7.返回被代理后的代理對(duì)象鸽凶。

好了原理梳理完了,下面就開(kāi)始自己動(dòng)手實(shí)現(xiàn)JDK動(dòng)態(tài)代理吧道伟。(ps:可能部分用語(yǔ)不專(zhuān)業(yè)蜜徽,大家理解下工科妹子的表達(dá)能力)

第1步和第2步完全同上拘鞋,這里就省略不寫(xiě)了啊盆色。

(1)創(chuàng)建自定義InvocationHandler
/**
 * 自定義InvocationHandler
 */
public interface LzzInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
(2)創(chuàng)建自定義一個(gè)ClassLoader
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * 自定義一個(gè)ClassLoader
 */
public class LzzClassLoader extends ClassLoader {
    private File baseDir;

    public LzzClassLoader() {
        String basePath = LzzClassLoader.class.getResource("").getPath();
        this.baseDir = new java.io.File(basePath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = LzzClassLoader.class.getPackage().getName() + "." + name;
        if (baseDir != null) {
            File classFile = new File(baseDir, name.replaceAll("\\.", "/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try {
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1) {
                        out.write(buff, 0, len);
                    }
                    return defineClass(className, out.toByteArray(), 0, out.size());

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (null != in) {
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (null != out) {
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    classFile.delete();
                }

            }
        }
        return null;
    }
}
(3)創(chuàng)建自定義Proxy類(lèi)
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * 自定義Proxy類(lèi)
 */
public class LzzProxy {

    // 換行符
    private static String ln = "\r\n";

    public static Object newProxyInstance(LzzClassLoader classLoader, Class<?>[] interfaces, LzzInvocationHandler h) {


        try {
            //1.生成源代碼
            String proxySrc = generateSrc(interfaces[0]);

            //2.保存將生成出來(lái)的源代碼
            String filePath = LzzProxy.class.getResource("").getPath();
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(proxySrc);
            fw.flush();
            fw.close();

            //3.編譯源代碼,生成.CLASS文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(f);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();

            //4.將class文件動(dòng)態(tài)加載到JVM返回被代理后的對(duì)象
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(LzzInvocationHandler.class);
            f.delete();

            return c.newInstance(h);

        } catch (Exception e) {
            e.printStackTrace();
        }


        return null;
    }


    private static String generateSrc(Class<?> interfaces) {
        StringBuffer src = new StringBuffer();
        src.append("package lzzly.custom;" + ln);
        src.append("import java.lang.reflect.Method;" + ln);
        src.append("public class $Proxy0 implements " + interfaces.getName() + "{" + ln);

        src.append("LzzInvocationHandler h;" + ln);

        src.append("public $Proxy0(LzzInvocationHandler h) {" + ln);
        src.append("this.h = h;" + ln);
        src.append("}" + ln);

        for (Method m : interfaces.getMethods()) {
            src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln);

            src.append("try{" + ln);
            src.append("Method m = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln);
            src.append("this.h.invoke(this,m,null);" + ln);
            src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
            src.append("}" + ln);
        }

        src.append("}");

        return src.toString();
    }
}
(4)創(chuàng)建CustomDynamicProxy自定義動(dòng)態(tài)代理類(lèi)
import java.lang.reflect.Method;

/**
 * 自定義動(dòng)態(tài)代理類(lèi)
 * 實(shí)現(xiàn)LzzInvocationHandler接口
 */
public class CustomDynamicProxy implements LzzInvocationHandler {

    // 被代理的對(duì)象
    Person target;

    // JDKDynamicProxy構(gòu)造函數(shù)
    public CustomDynamicProxy(Person person) {
        this.target = person;
    }

    // 獲取代理對(duì)象
    public Person getTarget() {
        return (Person) LzzProxy.newProxyInstance(new LzzClassLoader(), target.getClass().getInterfaces(), this);
    }

    // 動(dòng)態(tài)代理invoke方法
    public Person invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 被代理方法前執(zhí)行
        System.out.println("LzzDynamicProxy do something before!");
        // 執(zhí)行被代理的方法
        Person result = (Person) method.invoke(target, args);
        // 被代理方法后執(zhí)行
        System.out.println("LzzDynamicProxy do something after!");
        return result;
    }
}
(5)創(chuàng)建自定義動(dòng)態(tài)代理測(cè)試類(lèi)
/**
 * 自定義動(dòng)態(tài)代理測(cè)試
 */
public class CustomDynamicTest {

    public static void main(String[] args) {

        System.out.println("不使用代理類(lèi),調(diào)用doSomething方法叛薯。");
        // 不使用代理類(lèi)
        Person person = new Bob();
        // 調(diào)用doSomething方法
        person.doSomething();

        System.out.println("-------------------------------------- 分割線 --------------------------------------");

        System.out.println("使用代理類(lèi),調(diào)用doSomething方法组力。");
        // 獲取代理類(lèi)
        Person proxyPerson = new CustomDynamicProxy(new Bob()).getTarget();
        // 調(diào)用doSomething方法
        proxyPerson.doSomething();

    }
}

運(yùn)行結(jié)果如下圖所示:


image.png

項(xiàng)目源碼

碼云-DynamicProxy:https://gitee.com/OrgXxxx/DynamicProxy

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末燎字,一起剝皮案震驚了整個(gè)濱河市候衍,隨后出現(xiàn)的幾起案子脱柱,更是在濱河造成了極大的恐慌榨为,老刑警劉巖随闺,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矩乐,死亡現(xiàn)場(chǎng)離奇詭異散罕,居然都是意外死亡欧漱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)缚甩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)擅威,“玉大人郊丛,你說(shuō)我怎么就攤上這事宾袜。” “怎么了庆猫?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恩急。 經(jīng)常有香客問(wèn)我衷恭,道長(zhǎng),這世上最難降的妖魔是什么灭袁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮显沈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鳖藕。我一直安慰自己吊奢,他們只是感情好纹烹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著召边,像睡著了一般铺呵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隧熙,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天片挂,我揣著相機(jī)與錄音,去河邊找鬼贞盯。 笑死音念,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躏敢。 我是一名探鬼主播闷愤,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼件余!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旬渠,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤岖免,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體乙漓,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涩蜘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了霹俺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝗罗。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡打瘪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胸梆,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響雁乡,放射性物質(zhì)發(fā)生泄漏悠抹。R本人自食惡果不足惜卵凑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一黑忱、第九天 我趴在偏房一處隱蔽的房頂上張望抚吠。 院中可真熱鬧喊式,春花似錦贸诚、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至渣淳,卻和暖如春砂客,著一層夾襖步出監(jiān)牢的瞬間彤恶,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留子寓,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像掉瞳,于是被迫代替她去往敵國(guó)和親陕习。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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