動(dòng)態(tài)代理源碼分析

使用

  • 說(shuō)起動(dòng)態(tài)代理骏啰,大家都不陌生节吮,但對(duì)其原理卻一知半解。經(jīng)常遇到一個(gè)問(wèn)題判耕,java動(dòng)態(tài)代理為何只能適用接口透绩,why?你有考慮過(guò)其底層邏輯原因嗎壁熄?
  1. 首先看一個(gè)簡(jiǎn)單的使用
public class MyInvocationHandler implements InvocationHandler {

    private Object target ;

    public MyInvocationHandler(Object target) {
        super();
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在目標(biāo)對(duì)象的方法執(zhí)行之前簡(jiǎn)單的打印一下
        System.out.println("------------------before------------------");

        // 執(zhí)行目標(biāo)對(duì)象的方法
        Object result = method.invoke(target, args);

        // 在目標(biāo)對(duì)象的方法執(zhí)行之后簡(jiǎn)單的打印一下
        System.out.println("-------------------after------------------");

        return result;
    }


    /**
     * 獲取目標(biāo)對(duì)象的代理對(duì)象
     * @return 代理對(duì)象
     */
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(), this);
    }
}
  1. 以上渺贤,主要看Proxy.newProxyInstance代理方法
    • java對(duì)象創(chuàng)建過(guò)程,一般都是創(chuàng)建.java類通過(guò)javac編譯成.class文件请毛,通過(guò)類加載器創(chuàng)建對(duì)象初始化;


      image
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h){
        
    //獲得class對(duì)象 cl為 $Proxy0
    Class<?> cl = getProxyClass0(loader, intfs);
    
    //獲取構(gòu)造函數(shù)
    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;
            }
        });
    }
    //根據(jù)構(gòu)造函數(shù)反射調(diào)用初始化對(duì)象瞭亮,注意里面的h為實(shí)現(xiàn)MyInvocationHandler傳入的this
    return cons.newInstance(new Object[]{h});
                                          
}
  • 那么動(dòng)態(tài)代理是如何創(chuàng)建代理對(duì)象呢方仿?我們分析以上代碼可知:
    1. newProxyInstance中g(shù)etProxyClass0()獲取代理的.class文件,這里沒(méi)有了.java源碼,而是直接生成class文件统翩,通過(guò)class創(chuàng)建對(duì)象仙蚜;
    2. 有class文件后調(diào)用newInstance創(chuàng)建對(duì)象,注意構(gòu)造函數(shù)中傳入?yún)?shù)h為實(shí)現(xiàn)InvocationHandler
  1. 進(jìn)入getProxyClass0方法厂汗,記得參數(shù)loader : 類加載器委粉, interfaces當(dāng)前需要代理的接口數(shù)據(jù)
private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    
        return proxyClassCache.get(loader, interfaces);
}

public V get(K key, P parameter) {
       
        //從緩存中獲取,首次肯定沒(méi)有的
        ......

        // 創(chuàng)建類對(duì)象subKeyFactory在WeakCache初始化傳入
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        
    }

//Proxy中初始化緩存WeakCache
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

  • 肯定有個(gè)緩存快速查找啦娶桦,沒(méi)有就通過(guò)subKeyFactory.apply工廠類去新建啦贾节!
  1. 以上subKeyFactory.apply對(duì)應(yīng)的為ProxyClassFactory類中的apply方法: 生成代理類$Proxy0的class文件并返回
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * 如果當(dāng)前不是接口,拋出異常衷畦,但是并未說(shuō)明我們的疑問(wèn)
                 */
                 
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * 創(chuàng)建類名 $proxy + num自增加作為proxyName類名 .class
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * 生成類名class的byte數(shù)組
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                //native生成字節(jié)碼文件
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }
  • 生成class文件格式步驟為:
    1. 首先驗(yàn)證是否時(shí)接口栗涂,這里只是驗(yàn)證代理必須是接口,至于為何這里沒(méi)有顯示哦祈争;
    2. 創(chuàng)建.class文件的類名為$Proxy自增的num,首次為0斤程,這個(gè)我們待會(huì)可以看到的
    3. 通過(guò)ProxyGenerator.generateProxyClass生成byte數(shù)組后通過(guò)調(diào)用native方法defineClass0生成class文件
  1. 返回的為$Proxy0類
    1. 還記得2中cons.newInstance(new Object[]{h}),有上面class生成可知cons即為$Proxy0調(diào)用 newInstance構(gòu)造函數(shù)傳參為 h即 Proxy.newProxyInstance()中第三個(gè)參數(shù)h實(shí)現(xiàn)InvocationHandler的對(duì)象
    2. super(var1)將h傳給$Proxy0父類Proxy的h,因此可知$Proxy0中所調(diào)用的super.h即為我們自己寫的實(shí)現(xiàn)InvocationHandler的對(duì)象(很多地方用的是匿名內(nèi)部類)
public final class $Proxy0 extends Proxy implements UserService {
    //構(gòu)造函數(shù)中傳入的var1即為上方的h菩混,super即為Proxy
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
        
    //父類中的Proxy構(gòu)造函數(shù)h = MyInvocationHandler
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
}
  • 以上分析為何動(dòng)態(tài)代理只適用于接口忿墅,看我們生成的$Proxy0 必須要extends Proxy扁藕,而由于java的單繼承原則,因此不能在繼承類了疚脐,只能實(shí)現(xiàn)接口亿柑,因此只適用與接口;
  1. newProxyInstance返回的為代理生成class類的代理對(duì)象 $Proxy0后亮曹,調(diào)用add方法
    • $Proxy0.add() -> h為MyInvocationHandler.invoke()方法橄杨,將m3即接口方法m3 = Class.forName("test.UserService").getMethod("add"),在invoke方法中通過(guò)方式method.invoke() == m3.invoke(target , args) , result為方法返回值
public final void add() throws  {
    try {
        //h為MyInvocationHandler 調(diào)用其invoke方法
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}
  1. 通過(guò)以上調(diào)用了代理類中的invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 在目標(biāo)對(duì)象的方法執(zhí)行之前簡(jiǎn)單的打印一下
    System.out.println("------------------before------------------");

    // 執(zhí)行目標(biāo)對(duì)象的方法,result為方法的返回值
    Object result = method.invoke(target, args);

    // 在目標(biāo)對(duì)象的方法執(zhí)行之后簡(jiǎn)單的打印一下
    System.out.println("-------------------after------------------");

    return result;
}
  • 注意:這里的invoke是被$Proxy0代理類調(diào)用的照卦,參數(shù)method為$Proxy0代理類中靜態(tài)變量式矫,在invoke中調(diào)用method.invoke即反射調(diào)用實(shí)現(xiàn)接口類的方法,以上即為代理的完善源碼分析役耕!
  • 后續(xù):遇到一個(gè)有趣的問(wèn)題采转,好奇打印了一下被代理對(duì)象和代理類Proxy0,發(fā)現(xiàn)他們的地址也就是hashCode值是相同的,why?因?yàn)镻roxy0重寫了toString()方法并反射調(diào)用了被代理類的toString()瞬痘,因此兩者打印的完全一致故慈!遇到不理解的,還是得多多看源碼呀框全!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末察绷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子津辩,更是在濱河造成了極大的恐慌拆撼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喘沿,死亡現(xiàn)場(chǎng)離奇詭異闸度,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)蚜印,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門莺禁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人窄赋,你說(shuō)我怎么就攤上這事哟冬。” “怎么了忆绰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵柒傻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我较木,道長(zhǎng)红符,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮预侯,結(jié)果婚禮上致开,老公的妹妹穿的比我還像新娘。我一直安慰自己萎馅,他們只是感情好双戳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著糜芳,像睡著了一般飒货。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上峭竣,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天塘辅,我揣著相機(jī)與錄音,去河邊找鬼皆撩。 笑死扣墩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扛吞。 我是一名探鬼主播呻惕,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼滥比!你這毒婦竟也來(lái)了亚脆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盲泛,失蹤者是張志新(化名)和其女友劉穎型酥,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體查乒,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年郁竟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玛迄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棚亩,死狀恐怖蓖议,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讥蟆,我是刑警寧澤勒虾,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站瘸彤,受9級(jí)特大地震影響修然,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一愕宋、第九天 我趴在偏房一處隱蔽的房頂上張望玻靡。 院中可真熱鬧,春花似錦中贝、人聲如沸囤捻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝎土。三九已至,卻和暖如春绣否,著一層夾襖步出監(jiān)牢的瞬間誊涯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工枝秤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留醋拧,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓淀弹,卻偏偏與公主長(zhǎng)得像丹壕,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子薇溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353