不學無數(shù)——Java動態(tài)代理

動態(tài)代理

1. 什么是動態(tài)代理

在上一章節(jié)中糠赦,我們講的是代理其實都是靜態(tài)代理,動態(tài)代理是在運行階段動態(tài)的創(chuàng)建代理并且動態(tài)的處理對所代理方法的調用慨畸。在動態(tài)代理上所做的所有調用都會被重定向到單一的調用處理器中滨达。在現(xiàn)在很流行的Spring中有一個AOP(面向切面)的其中核心實現(xiàn)技術就是動態(tài)代理的技術。

2. 為什么要用動態(tài)代理

動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理赡盘,而不用修改每個代理類中的方法。例如我們想計算出每一個方法的執(zhí)行時間缰揪,如果使用靜態(tài)代理的話陨享,那么就需要在每一個代理類中進行更改,但是如果使用了動態(tài)代理可以對類的所有方法進行統(tǒng)一的管理钝腺。一處添加抛姑,所有方法適用。

3. 動態(tài)代理的簡單實現(xiàn)

3.1 靜態(tài)代理的實現(xiàn)

我們先看一下靜態(tài)代理的是如何實現(xiàn)的艳狐,關于靜態(tài)代理詳細的解釋可以看不學無數(shù)——Java代理模式定硝,這里只貼出關于靜態(tài)代理的一些代碼。

Homeowner接口如下:

interface Homeowner{
    public void LeaseHouse(Home home);
}

RealHomeowner類如下

class RealHomeowner implements Homeowner{

    @Override
    public void LeaseHouse(Home home) {
        System.out.println("房價是: "+ home.getPrice());
        System.out.println("房子顏色是: "+ home.getColor());
        System.out.println("房子出租成功");
    }
}

代理類HomeProxy的實現(xiàn)

class HomeProxy implements Homeowner{

    private Homeowner homeowner;
    public HomeProxy(Homeowner homeowner){
        this.homeowner = homeowner;
    }

    @Override
    public void LeaseHouse(Home home) {
        System.out.println("中介干預");
        homeowner.LeaseHouse(home);
        System.out.println("中介干預完成");
    }
}

在main方法中使用

public static void main(String[] args) {
    Home home = new Home("red",1000);
    RealHomeowner realHomeowner = new RealHomeowner();
    Homeowner homeowner = new HomeProxy(realHomeowner);
    homeowner.LeaseHouse(home);
}

打印的信息如下:

中介干預
房價是: 1000
房子顏色是: red
房子出租成功
中介干預完成

3.2 動態(tài)代理的實現(xiàn)

在動態(tài)代理中是不需要代理類的毫目,就是不需要上面靜態(tài)代理中的HomeProxy類蔬啡,通過實現(xiàn)了InvocationHandler類,所有方法都由該'Handler'來處理了镀虐,意思就是所有被代理的方法都由InvocationHandler接管實際的處理任務箱蟆。那么看實際的例子

DynamicPro

class DynamicPro implements InvocationHandler{

    //真實被代理的實例對象
    private Object object;

    public DynamicPro(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("中介干預");
        Object result = method.invoke(object,args);
        System.out.println("中介干預完成");
        return result;
    }
}

在主方法如下的調用

public static void main(String[] args) {
        Home home = new Home("red",1000);
        //創(chuàng)建一個被代理的實例對象
        RealHomeowner realHomeowner = new RealHomeowner();
        //創(chuàng)建一個與被代理對象相關的InvocationHandler
        DynamicPro dynamicPro = new DynamicPro(realHomeowner);
        //創(chuàng)建一個類加載器
        ClassLoader classLoader = realHomeowner.getClass().getClassLoader();
        //被代理類的接口數(shù)組,里面的每一個方法都會執(zhí)行InvocationHandler中的invoke方法
        Class<?>[] proxInterface = realHomeowner.getClass().getInterfaces();
        Homeowner homeowner = (Homeowner) Proxy.newProxyInstance(classLoader,proxInterface,dynamicPro);
        homeowner.LeaseHouse(home);
}

打印如下

中介干預
房價是: 1000
房子顏色是: red
房子出租成功
中介干預完成

4. 動態(tài)代理和靜態(tài)代理的區(qū)別

動態(tài)代理類圖

上面是關于動態(tài)代理的類圖,我們可以和靜態(tài)代理的類圖進行對比一下

靜態(tài)代理類圖

可以看到在動態(tài)代理中不需要了實際的代理角色類刮便,因為實際的代理角色在動態(tài)代理中時動態(tài)生成的空猜。在動態(tài)代理中增加了InvocationHandler接口類,這個接口中只有一個方法,就是invoke()方法辈毯。我們可以實現(xiàn)InvocationHandler類然后在invoke()方法中對調用實際方法時的前置或者后置處理久信。

5. 動態(tài)代理原理簡單分析

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

通過上面的代碼的Debug發(fā)現(xiàn)最后是通過下面代碼返回一個對象的

 //返回構造器生成的實例對象
 return cons.newInstance(new Object[]{h});

然后發(fā)現(xiàn)cons是從下面的代碼獲得的

//獲得此代理類的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);

cl是從下面的代碼中獲得的

//查找或生成指定的代理類
Class<?> cl = getProxyClass0(loader, intfs);

而intfs是從下面的代碼獲得

//克隆一個接口類
final Class<?>[] intfs = interfaces.clone();

隨后想進去看getProxyClass0生成的代理類是什么,但是發(fā)現(xiàn)進不去漓摩。后來查資料知道它由于是動態(tài)生成的裙士,類是緩存在java虛擬機中的,可以通過下面的方法將類打印出來管毙。

動態(tài)代理類的字節(jié)碼在程序運行時由Java反射機制動態(tài)生成


    public static void main(String[] args) {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Homeowner.class.getInterfaces());
        String path = "/Users/hupengfei/git/Test/src/main/java/Practice/Day06/Homeowner.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理類class文件寫入成功");
        } catch (Exception e) {
            System.out.println("寫文件錯誤");
        }
    }

對生成的class文件進行反編譯腿椎,在Idea中能直接查看


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import Practice.Day06.Home;
import Practice.Day06.Homeowner;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Homeowner {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void LeaseHouse(Home var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final Homeowner getProxy() throws  {
        try {
            return (Homeowner)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Practice.Day06.Homeowner").getMethod("getProxy");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

首先我們可以先看生成的此類的構造函數(shù)

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    -----調用了父類的構造函數(shù),而它的父類是Proxy類夭咬,父類的構造函數(shù)如下
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    --在父類中的h的定義如下
    protected InvocationHandler h;

此時我們就知道為什么我們的動態(tài)代理都會執(zhí)行傳入的InvocationHandler中的invoke()方法了

在下面的靜態(tài)代碼塊中我們發(fā)現(xiàn)了LeaseHouse()方法

 m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));

然后在上面會發(fā)現(xiàn)有我們的方法

public final void LeaseHouse(Home var1) throws  {
    try {
        super.h.invoke(this, m4, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}

此時我們再回想一下在InvocationHandler類中的invoke()方法中傳入的參數(shù)有Method方法了啃炸,這樣就可以將外部對于被代理對象的調用都轉化為調用invoke()方法,再由invoke()方法中調用被代理對象的方法卓舵。

6. 參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末南用,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子掏湾,更是在濱河造成了極大的恐慌裹虫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件融击,死亡現(xiàn)場離奇詭異筑公,居然都是意外死亡,警方通過查閱死者的電腦和手機尊浪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門匣屡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拇涤,你說我怎么就攤上這事捣作。” “怎么了鹅士?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵券躁,是天一觀的道長。 經常有香客問我如绸,道長嘱朽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任怔接,我火速辦了婚禮,結果婚禮上稀轨,老公的妹妹穿的比我還像新娘扼脐。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布瓦侮。 她就那樣靜靜地躺著艰赞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肚吏。 梳的紋絲不亂的頭發(fā)上方妖,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音罚攀,去河邊找鬼党觅。 笑死,一個胖子當著我的面吹牛斋泄,可吹牛的內容都是我干的杯瞻。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼炫掐,長吁一口氣:“原來是場噩夢啊……” “哼魁莉!你這毒婦竟也來了?” 一聲冷哼從身側響起募胃,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤旗唁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后痹束,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逆皮,經...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年参袱,在試婚紗的時候發(fā)現(xiàn)自己被綠了电谣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡抹蚀,死狀恐怖剿牺,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情环壤,我是刑警寧澤晒来,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站郑现,受9級特大地震影響湃崩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜接箫,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一攒读、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辛友,春花似錦薄扁、人聲如沸剪返。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脱盲。三九已至,卻和暖如春日缨,著一層夾襖步出監(jiān)牢的瞬間钱反,已是汗流浹背匣距。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工面哥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留墨礁,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓恩静,卻偏偏與公主長得像焕毫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子驶乾,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內容