真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

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

本篇內(nèi)容主要講解“什么是JDK動(dòng)態(tài)代理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“什么是JDK動(dòng)態(tài)代理”吧!

林芝網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián)公司,林芝網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為林芝上千多家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的林芝做網(wǎng)站的公司定做!

JDK動(dòng)態(tài)代理是指:代理類實(shí)例在程序運(yùn)行時(shí),由JVM根據(jù)反射機(jī)制動(dòng)態(tài)的生成。也就是說代理類不是用戶自己定義的,而是由JVM生成的。

由于其原理是通過Java反射機(jī)制實(shí)現(xiàn)的,所以在學(xué)習(xí)前,要對(duì)反射機(jī)制有一定的了解。傳送門:Java反射機(jī)制:跟著代碼學(xué)反射

下面是本篇講述內(nèi)容: 什么是JDK動(dòng)態(tài)代理

1. JDK動(dòng)態(tài)代理的核心類

JDK動(dòng)態(tài)代理有兩大核心類,它們都在Java的反射包下(java.lang.reflect),分別為InvocationHandler接口和Proxy類。

1.1 InvocationHandler接口

代理實(shí)例的調(diào)用處理器需要實(shí)現(xiàn)InvocationHandler接口,并且每個(gè)代理實(shí)例都有一個(gè)關(guān)聯(lián)的調(diào)用處理器。當(dāng)一個(gè)方法在代理實(shí)例上被調(diào)用時(shí),這個(gè)方法調(diào)用將被編碼并分派到其調(diào)用處理器的invoke方法上。

也就是說,我們創(chuàng)建的每一個(gè)代理實(shí)例都要有一個(gè)關(guān)聯(lián)的InvocationHandler,并且在調(diào)用代理實(shí)例的方法時(shí),會(huì)被轉(zhuǎn)到InvocationHandlerinvoke方法上。

public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

invoke方法的作用是:處理代理實(shí)例上的方法調(diào)用并返回結(jié)果。

其有三個(gè)參數(shù),分別為:

  • proxy:是調(diào)用該方法的代理實(shí)例。

  • method:是在代理實(shí)例上調(diào)用的接口方法對(duì)應(yīng)的Method實(shí)例。

  • args:一個(gè)Object數(shù)組,是在代理實(shí)例上的方法調(diào)用中傳遞的參數(shù)值。如果接口方法為無參,則該值為null。

其返回值為:調(diào)用代理實(shí)例上的方法的返回值。

1.2 Proxy類

Proxy類提供了創(chuàng)建動(dòng)態(tài)代理類及其實(shí)例的靜態(tài)方法,該類也是動(dòng)態(tài)代理類的超類。

代理類具有以下屬性:

  • 代理類的名稱以 “$Proxy” 開頭,后面跟著一個(gè)數(shù)字序號(hào)。

  • 代理類繼承了Proxy類。

  • 代理類實(shí)現(xiàn)了創(chuàng)建時(shí)指定的接口(JDK動(dòng)態(tài)代理是面向接口的)。

  • 每個(gè)代理類都有一個(gè)公共構(gòu)造函數(shù),它接受一個(gè)參數(shù),即接口InvocationHandler的實(shí)現(xiàn),用于設(shè)置代理實(shí)例的調(diào)用處理器。

Proxy提供了兩個(gè)靜態(tài)方法,用于獲取代理對(duì)象。

1.2.1 getProxyClass

用于獲取代理類的Class對(duì)象,再通過調(diào)用構(gòu)造函數(shù)創(chuàng)建代理實(shí)例。

public static Class getProxyClass(ClassLoader loader,
                                         Class... interfaces)
        throws IllegalArgumentException

該方法有兩個(gè)參數(shù):

  • loader:為類加載器。

  • intefaces:為接口的Class對(duì)象數(shù)組。

返回值為動(dòng)態(tài)代理類的Class對(duì)象。

1.2.2 newProxyInstance

用于創(chuàng)建一個(gè)代理實(shí)例。

public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

該方法有三個(gè)參數(shù):

  • loader:為類加載器。

  • interfaces:為接口的Class對(duì)象數(shù)組。

  • h:指定的調(diào)用處理器。

返回值為指定接口的代理類的實(shí)例。

1.3 小結(jié)

Proxy類主要用來獲取動(dòng)態(tài)代理對(duì)象,InvocationHandler接口主要用于方法調(diào)用的約束與增強(qiáng)。

2. 獲取代理實(shí)例的代碼示例

上一章中已經(jīng)介紹了獲取代理實(shí)例的兩個(gè)靜態(tài)方法,現(xiàn)在通過代碼示例來演示具體實(shí)現(xiàn)。

2.1 創(chuàng)建目標(biāo)接口及其實(shí)現(xiàn)類

JDK動(dòng)態(tài)代理是基于接口的,我們創(chuàng)建一個(gè)接口及其實(shí)現(xiàn)類。

Foo接口:

public interface Foo {

    String ping(String name);

}

Foo接口的實(shí)現(xiàn)類RealFoo:

public class RealFoo implements Foo {

    @Override
    public String ping(String name) {
        System.out.println("ping");
        return "pong";
    }

}

2.2 創(chuàng)建一個(gè)InvocationHandler

創(chuàng)建一個(gè)InvocationHandler接口的實(shí)現(xiàn)類MyInvocationHandler。該類的構(gòu)造方法參數(shù)為要代理的目標(biāo)對(duì)象。

invoke方法中的三個(gè)參數(shù)上面已經(jīng)介紹過,通過調(diào)用methodinvoke方法來完成方法的調(diào)用。

這里一時(shí)看不懂沒關(guān)系,后面源碼解析章節(jié)會(huì)進(jìn)行剖析。

public class MyInvocationHandler implements InvocationHandler {

    // 目標(biāo)對(duì)象
    private final Object target;

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


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy - " + proxy.getClass());
        System.out.println("method - " + method);
        System.out.println("args - " + Arrays.toString(args));
        return method.invoke(target, args);
    }
}

2.3 方式一:通過getProxyClass方法獲取代理實(shí)例

具體實(shí)現(xiàn)步驟如下:

  1. 根據(jù)類加載器和接口數(shù)組獲取代理類的Class對(duì)象

  2. 過Class對(duì)象的構(gòu)造器創(chuàng)建一個(gè)實(shí)例(代理類的實(shí)例)

  3. 將代理實(shí)例強(qiáng)轉(zhuǎn)成目標(biāo)接口Foo(因?yàn)榇眍悓?shí)現(xiàn)了目標(biāo)接口,所以可以強(qiáng)轉(zhuǎn))。

  4. 最后使用代理進(jìn)行方法調(diào)用。

@Test
public void test1() throws Exception {
    Foo foo = new RealFoo();
    // 根據(jù)類加載器和接口數(shù)組獲取代理類的Class對(duì)象
    Class proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);

    // 通過Class對(duì)象的構(gòu)造器創(chuàng)建一個(gè)實(shí)例(代理類的實(shí)例)
    Foo fooProxy = (Foo) proxyClass.getConstructor(InvocationHandler.class)
        .newInstance(new MyInvocationHandler(foo));

    // 調(diào)用 ping 方法,并輸出返回值
    String value = fooProxy.ping("楊過");
    System.out.println(value);

}

輸出結(jié)果:

proxy - class com.sun.proxy.$Proxy4
method - public abstract java.lang.String io.github.gozhuyinglong.proxy.Foo.ping(java.lang.String)
args - [楊過]
ping
pong

通過輸出結(jié)果可以看出:

  • 代理類的名稱是以$Proxy開頭的。

  • 方法實(shí)例為代理類調(diào)用的方法。

  • 參數(shù)為代理類調(diào)用方法時(shí)傳的參數(shù)。

2.4 方式二:通過newProxyInstance方法獲取代理實(shí)例

通過這種方法是最簡(jiǎn)單的,也是推薦使用的,通過該方法可以直接獲取代理對(duì)象。

注:其實(shí)該方法后臺(tái)實(shí)現(xiàn)實(shí)際與上面使用getProxyClass方法的過程一樣。

@Test
public void test2() {
    Foo foo = new RealFoo();
    // 通過類加載器、接口數(shù)組和調(diào)用處理器,創(chuàng)建代理類的實(shí)例
    Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                                new Class[]{Foo.class},
                                                new MyInvocationHandler(foo));
    String value = fooProxy.ping("小龍女");
    System.out.println(value);
}

2.5 通過Lambda表達(dá)式簡(jiǎn)化實(shí)現(xiàn)

其實(shí)InvocationHander接口也不用創(chuàng)建一個(gè)實(shí)現(xiàn)類,可以使用Lambad表達(dá)式進(jìn)行簡(jiǎn)化的實(shí)現(xiàn),如下代碼:

@Test
public void test3() {
    Foo foo = new RealFoo();

    Foo fooProxy = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
                                                new Class[]{Foo.class},
                                                (proxy, method, args) -> method.invoke(foo, args));
    String value = fooProxy.ping("雕兄");
    System.out.println(value);
}

3. 源碼解析

3.1 代理類$Proxy是什么樣子

JVM為我們自動(dòng)生成的代理類到底是什么樣子的呢?下面我們先來生成一下,再來看里面的構(gòu)造。

3.1.1 生成$Proxy的.class文件

JVM默認(rèn)不創(chuàng)建該.class文件,需要增加一個(gè)啟動(dòng)參數(shù): -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

在IDEA中點(diǎn)擊【Edit Configurations...】,打開 Run/Debug Configurations 配置框。

什么是JDK動(dòng)態(tài)代理 將上面啟動(dòng)參數(shù)加到【VM options】中,點(diǎn)擊【OK】即可。 什么是JDK動(dòng)態(tài)代理

再次運(yùn)行代碼,會(huì)在項(xiàng)目中的【com.sun.proxy】目錄中找到這個(gè).class文件,我這里是“$Proxy4.class” 什么是JDK動(dòng)態(tài)代理

3.1.2 為什么加上這段啟動(dòng)參數(shù)就能生成$Proxy的字節(jié)碼文件

Proxy類中有個(gè)ProxyClassFactory靜態(tài)內(nèi)部類,該類主要作用就是生成靜態(tài)代理的。

其中有一段代碼ProxyGenerator.generateProxyClass用來生成代理類的.class文件。 什么是JDK動(dòng)態(tài)代理

其中變量saveGeneratedFiles便是引用了此啟動(dòng)參數(shù)的值。將該啟動(dòng)參數(shù)配置為true會(huì)生成.class文件。 什么是JDK動(dòng)態(tài)代理

3.1.3 這個(gè)代理類$Proxy到底是什么樣子呢

神秘的面紗即將揭露,前面很多未解之迷在這里可以找到答案!

打開這個(gè)$Proxy文件,我這里生成的是$Proxy4,下面是內(nèi)容:

// 該類為final類,其繼承了Proxy類,并實(shí)現(xiàn)了被代理接口Foo
public final class $Proxy4 extends Proxy implements Foo {

    // 這4個(gè)Method實(shí)例,代表了本類實(shí)現(xiàn)的4個(gè)方法
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    // 靜態(tài)代碼塊根據(jù)反射獲取這4個(gè)方法的Method實(shí)例
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("io.github.gozhuyinglong.proxy.Foo").getMethod("ping");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    // 一個(gè)公開的構(gòu)造函數(shù),參數(shù)為指定的 InvocationHandler 
    public $Proxy4(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 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);
        }
    }

    // Foo接口的實(shí)現(xiàn)方法,最終調(diào)用了 InvocationHandler 中的 invoke 方法
    public final String ping(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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);
        }
    }
}

通過該文件可以看出:

  • 代理類繼承了Proxy類,其主要目的是為了傳遞InvocationHandler。

  • 代理類實(shí)現(xiàn)了被代理的接口Foo,這也是為什么代理類可以直接強(qiáng)轉(zhuǎn)成接口的原因。

  • 有一個(gè)公開的構(gòu)造函數(shù),參數(shù)為指定的InvocationHandler,并將參數(shù)傳遞到父類Proxy中。

  • 每一個(gè)實(shí)現(xiàn)的方法,都會(huì)調(diào)用InvocationHandler中的invoke方法,并將代理類本身、Method實(shí)例、入?yún)⑷齻€(gè)參數(shù)進(jìn)行傳遞。這也是為什么調(diào)用代理類中的方法時(shí),總會(huì)分派到InvocationHandler中的invoke方法的原因。

3.2 代理類是如何創(chuàng)建的

我們從Proxy類為我們提供的兩個(gè)靜態(tài)方法開始getProxyClassnewProxyInstance。上面已經(jīng)介紹了,這兩個(gè)方法是用來創(chuàng)建代理類及其實(shí)例的,下面來看源碼。

3.2.1 getProxyClass 和 newProxyInstance方法

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

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

通過上面源碼可以看出,這兩個(gè)方法最終都會(huì)調(diào)用getProxyClass0方法來生成代理類的Class對(duì)象。只不過newProxyInstance方法為我們創(chuàng)建好了代理實(shí)例,而getProxyClass方法需要我們自己創(chuàng)建代理實(shí)例。

3.2.2 getProxyClass0 方法

下面來看這個(gè)統(tǒng)一的入口:getProxyClass0 什么是JDK動(dòng)態(tài)代理

從源碼和注解可以看出:

  • 代理接口的最多不能超過65535個(gè)

  • 會(huì)先從緩存中獲取代理類,則沒有再通過ProxyClassFactory創(chuàng)建代理類。(代理類會(huì)被緩存一段時(shí)間。)

3.2.3 WeakCache類

這里簡(jiǎn)單介紹一下WeakCache 類,該類主要是為代理類進(jìn)行緩存的。獲取代理類時(shí),會(huì)首先從緩存中獲取,若沒有會(huì)調(diào)用ProxyClassFactory類進(jìn)行創(chuàng)建,創(chuàng)建好后會(huì)進(jìn)行緩存。 什么是JDK動(dòng)態(tài)代理

3.2.4 ProxyClassFactory類

ProxyClassFactoryProxy類的一個(gè)靜態(tài)內(nèi)部類,該類用于生成代理類。下圖是源碼的部分內(nèi)容:

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

  • 代理類的名稱就是在這里定義的,其前綴是$Proxy,后綴是一個(gè)數(shù)字。

  • 調(diào)用ProxyGenerator.generateProxyClass來生成指定的代理類。

  • defineClass0方法是一個(gè)native方法,負(fù)責(zé)字節(jié)碼加載的實(shí)現(xiàn),并返回對(duì)應(yīng)的Class對(duì)象。

3.3 原理圖

為了便于記錄,將代理類的生成過程整理成了一張圖。

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

到此,相信大家對(duì)“什么是JDK動(dòng)態(tài)代理”有了更深的了解,不妨來實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!


網(wǎng)站題目:什么是JDK動(dòng)態(tài)代理
URL鏈接:http://weahome.cn/article/jhcgho.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部