Java動態(tài)代理原理和源碼分析

導讀

代理模式就是自己做不了或不想做的事情找別人做迹蛤,比如我們買不到票,找黃牛買襟士,這就是代理模式盗飒。

代理模式分為調用方、代理陋桂、目標三部分逆趣。

我們常用的Java代理模式主要有兩種:

  • 靜態(tài)代理

  • 動態(tài)代理

靜態(tài)代理是設計模式中的一種,也就是硬編碼嗜历,一旦需要代理的類或方法多了宣渗,操作使用很不方便。今天主要講動態(tài)代理 梨州。

基本用法

場景痕囱,普通粉絲通過黃牛購買演唱會的門票,
先看一下代碼實現(xiàn)

接口類 TicketCenter.java

public interface TicketCenter {
    void buyTicket(Integer amount);
    void refundTicket();
    void sellTicket();
}

接口實現(xiàn)類 RealTicketCenter.java

public class RealTicketCenter implements TicketCenter {

    @Override
    public void buyTicket(Integer amount) {
        System.out.println("buyTicket,need ¥" + amount);
    }

    @Override
    public void refundTicket() {
        System.out.println("refundTicket");
    }

    @Override
    public void sellTicket() {
        System.out.println("sellTicket");
    }
}

代理類 ProxyTicketCenter.java

public class ProxyTicketCenter implements InvocationHandler {
    RealTicketCenter realTicketCenter;

    public ProxyTicketCenter(RealTicketCenter realTicketCenter) {
        this.realTicketCenter = realTicketCenter;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("before");
        Object object = null;
        object=method.invoke(realTicketCenter,args);
        System.out.println("after");
        return object;
    }
}

使用 Main.java

public class Main {
    public static void main(String args[]){
        //保留生成的字節(jié)碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        TicketCenter ticketCenter = (TicketCenter) Proxy.newProxyInstance(Main.class.getClassLoader(),new Class[]{TicketCenter.class},new ProxyTicketCenter(new RealTicketCenter()));
        ticketCenter.buyTicket(10);
    }
}

執(zhí)行結果如下

before
buyTicket,need ¥10
after

源碼分析

我們可以看到在執(zhí)行buyTicket方法前后執(zhí)行了我們代理需要做的事情暴匠。
我們進入到newProxyInstance方法


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

這里是找到或生成指定的代理類鞍恢,
進入到getProxyClass0方法

   /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

從緩存中獲取,如果沒有則生成每窖,我們看一下proxyClassCache這個對象帮掉,存的是代理類緩存


    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

這里重點要看下ProxyClassFactory,代理類工廠岛请,是如何生成的

            /*
             * Generate the specified proxy class.
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);

看到這段代碼旭寿,就豁然開朗了,生成代理類的字節(jié)碼崇败,那么一個代理類就生成了盅称,有興趣的同學可以進入generateProxyClass方法看他是怎么生成的肩祥。
那生成的代碼是如何的呢?
我們在Main類有一段代碼就起作用了

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

這段代碼會將生成的字節(jié)碼保存到本地
一起看下生成的關鍵代碼


public final class $Proxy0 extends Proxy implements TicketCenter {
    private static Method m1;
    private static Method m5;
    private static Method m2;
    private static Method m3;
    private static Method m4;
    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 refundTicket() throws  {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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 void buyTicket(Integer var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sellTicket() throws  {
        try {
            super.h.invoke(this, m4, (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"));
            m5 = Class.forName("TicketCenter").getMethod("refundTicket");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("TicketCenter").getMethod("buyTicket", Class.forName("java.lang.Integer"));
            m4 = Class.forName("TicketCenter").getMethod("sellTicket");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

我們在Main方法調用的 ticketCenter.buyTicket(10);
其實就是調用了這個類里面的buyTicket方法缩膝,在buyTicket方法里調用了InvocationHandler 的invoke方法混狠。這樣一來,我們就明白了是動態(tài)代理是如何實現(xiàn)的了疾层。
源碼 見lean-proxy模塊 https://github.com/HuangPugang/Java-lean

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末将饺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子痛黎,更是在濱河造成了極大的恐慌予弧,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件湖饱,死亡現(xiàn)場離奇詭異掖蛤,居然都是意外死亡,警方通過查閱死者的電腦和手機井厌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門蚓庭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仅仆,你說我怎么就攤上這事器赞。” “怎么了墓拜?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵港柜,是天一觀的道長。 經常有香客問我撮弧,道長潘懊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任贿衍,我火速辦了婚禮授舟,結果婚禮上,老公的妹妹穿的比我還像新娘贸辈。我一直安慰自己释树,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布擎淤。 她就那樣靜靜地躺著奢啥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘴拢。 梳的紋絲不亂的頭發(fā)上桩盲,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音席吴,去河邊找鬼赌结。 笑死捞蛋,一個胖子當著我的面吹牛,可吹牛的內容都是我干的柬姚。 我是一名探鬼主播拟杉,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼量承!你這毒婦竟也來了搬设?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤撕捍,失蹤者是張志新(化名)和其女友劉穎拿穴,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體忧风,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡贞言,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阀蒂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡弟蚀,死狀恐怖蚤霞,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情义钉,我是刑警寧澤昧绣,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站捶闸,受9級特大地震影響夜畴,放射性物質發(fā)生泄漏。R本人自食惡果不足惜删壮,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一贪绘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧央碟,春花似錦税灌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至洛勉,卻和暖如春粘秆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背收毫。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工攻走, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留殷勘,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓陋气,卻偏偏與公主長得像劳吠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巩趁,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359