欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于JDK動態(tài)代理原理解析

 更新時間:2023年05月05日 08:59:45   作者:wen-pan  
這篇文章主要介紹了基于JDK動態(tài)代理原理解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

1、回顧一下JDK動態(tài)代理的核心參數(shù)

如果我們要為target類創(chuàng)建一個【JDK動態(tài)代理對象】,那么我們必須要傳入如下三個核心參數(shù)

  • 加載target類的類加載器
  • target類實現(xiàn)的接口
  • InvocationHandler

為什么必須要這三個參數(shù)呢?之前使用動態(tài)代理的時候都是直接按接口要求傳這三個參數(shù),但從來沒想過為什么?下面仔細去探究一下

2、實現(xiàn)一個簡單的動態(tài)代理

【JDK動態(tài)代理】的核心其實是借助【Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)】方法,去創(chuàng)建的動態(tài)代理對象,我們這里也使用這個方法去創(chuàng)建一個簡單的【動態(tài)代理對象】以便于理解他的核心原理。

①、定義接口Subject

public interface Subject {
    /**
     * 接口方法
     */
    void doSomething();
    /**
     * sayHello
     *
     * @param name name
     * @return string
     */
    String sayHello(String name);
}

②、定義接口的實現(xiàn)類RealSubject

實現(xiàn)Subject接口,并實現(xiàn)接口相關(guān)方法

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject do something");
    }
    @Override
    public String sayHello(String name) {
        System.out.println("RealSubject sayHello");
        return "hello-" + name;
    }
}

③、定義代理對象創(chuàng)建工廠

  • 定義一個工廠類,該工廠類用于為target對象生產(chǎn)代理對象
  • 該工廠類借助Proxy.newProxyInstance來為目標對象創(chuàng)建代理對象
public class JdkDynamicProxyFactory {
    /**
     * 創(chuàng)建target類的代理對象
     * 注意:當(dāng)調(diào)用代理對象中的方法時,其實就是調(diào)用的InvocationHandler里面的invoke方法,然后在invoke方法里調(diào)用目標對象對應(yīng)的方法
     *
     * @param <T> 泛型
     * @return 代理對象
     */
    public static <T> T getProxy(Object target) {
        // 創(chuàng)建代理實例,分別傳入:【加載target類的類加載器、target類實現(xiàn)的接口、InvocationHandler】
        Object proxyInstance = Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("執(zhí)行目標方法前");
                        // 執(zhí)行目標方法
                        Object result = method.invoke(target, args);
                        System.out.println("執(zhí)行目標方法后");
                        // 返回目標方法的執(zhí)行結(jié)果
                        return result;
                    }
                });
        // 返回代理對象
        return (T) proxyInstance;
    }
}

④、創(chuàng)建測試類,為target創(chuàng)建代理對象

該測試類會將內(nèi)存中的動態(tài)代理對象保存到磁盤上,以便于我們后續(xù)分析生成的動態(tài)代理類的具體結(jié)構(gòu)

public class Client {
    public static void main(String[] args) {
        // 保存生成的代理類的字節(jié)碼文件
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 目標對象
        RealSubject target = new RealSubject();
        // 使用JDK動態(tài)代理為【target對象】創(chuàng)建代理對象
        Subject proxy = JdkDynamicProxyFactory.getProxy(target);
        // 調(diào)用代理對象的方法
        proxy.doSomething();
        System.out.println("=====================華麗的分割線=====================");
        proxy.sayHello("wenpan");
    }
}

3、分析生成的動態(tài)代理類的結(jié)構(gòu)

在上面一步中我們使用

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

將動態(tài)生成的代理保存到了磁盤上,下面我們就具體看看生成的代理類長什么樣

  • 可以看到代理類【繼承了Proxy類】,并且【實現(xiàn)了目標接口Subject】,覆寫了接口中的【每一個方法】
  • 這也說明了為什么JDK代理需要實現(xiàn)接口,因為Java是單繼承的,既然代理類繼承了Proxy類那么就無法再繼承其他類了
  • 在代理類中將我們的【目標接口Subject】的【所有方法(包括object父類的方法)】都以【靜態(tài)屬性的形式】保存了起來(主要是為了方便后面的【反射調(diào)用】)
  • 在調(diào)用動態(tài)代理對象的某個方法時(比如:調(diào)用doSomething方法),實質(zhì)上是調(diào)用的【Proxy類】的【h屬性】的invoke方法
  • 所以我們要重點去看看這個【Proxy.h】到底是個什么鬼?其實他就是創(chuàng)建代理對象是我們傳入的【InvocationHandler】
// 1、代理類首先繼承了Proxy類(這也說明了為什么JDK代理需要實現(xiàn)接口,因為Java是單繼承的),并且實現(xiàn)了目標接口Subject
public final class $Proxy0 extends Proxy implements Subject {
  	// 2、可以看到,在代理類中將我們的【目標接口Subject】的【所有方法(包括object父類的方法)】都以【靜態(tài)屬性的形式】保存了起來
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    private static Method m0;
  	// 以靜態(tài)代碼塊的形式為屬性賦值
    static {
          try {
              m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
              m3 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("doSomething");
              m4 = Class.forName("com.stone.design.mode.proxy.jdk.Subject").getMethod("sayHello", Class.forName("java.lang.String"));
              m2 = Class.forName("java.lang.Object").getMethod("toString");
              m0 = Class.forName("java.lang.Object").getMethod("hashCode");
          } catch (NoSuchMethodException var2) {
              throw new NoSuchMethodError(var2.getMessage());
          } catch (ClassNotFoundException var3) {
              throw new NoClassDefFoundError(var3.getMessage());
          }
      }
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
  	// 3、object父類的equals方法
    public final boolean equals(Object var1) throws  {
        try {
          	// 這里的supper是指的Proxy類,調(diào)用【Proxy類】的【h屬性】的invoke方法執(zhí)行
          	// 重點:【注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看
          	// 這里也就體現(xiàn)了創(chuàng)建代理對象時為什么需要傳入【InvocationHandler】,以及為什么調(diào)用代理對象的方法時都是執(zhí)行的invoke方法
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }
  	// 4、目標接口的方法
    public final void doSomething() throws  {
        try {
          	// 調(diào)用了【Proxy.h屬性】的invoke方法
          	// 注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
  	// 5、目標接口的方法
    public final String sayHello(String var1) throws  {
        try {
          	// 調(diào)用了【Proxy.h屬性】的invoke方法
          	// 注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看
            return (String)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 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);
        }
    }
}

4、Proxy.h是什么鬼

在上面的第三點中我們看到,執(zhí)行代理對象的方法的時候其實質(zhì)是調(diào)用的【super.h.invoke方法】也就是【Proxy.h.invoke方法】,那么我們仔細看看【Proxy.h】到底是什么鬼

①、Proxy.newProxyInstance是如何創(chuàng)建代理對象的

下面的源代碼我做了一些刪減,只留下了最核心的部分

  • 通過下面代碼我們就明確了使用【newProxyInstance】方法創(chuàng)建代理對象時所做的幾件事情
  • 首先通過【目標接口】 + 【類加載器】創(chuàng)建一個Proxy類的【Class對象】
  • 然后通過這個【Class對象】獲取到他的有參構(gòu)造器,并且傳入我們自定義的【InvocationHandler】作為構(gòu)造函數(shù)參數(shù),并且通過反射的方式調(diào)用有參構(gòu)造器創(chuàng)建一個【Proxy對象】
  • 在【Proxy的有參構(gòu)造器】中,會將傳入的【InvocationHandler】保存到 【h屬性】上(方便后面的supper.h.invoke調(diào)用)
  • 代理對象創(chuàng)建完畢
public class Proxy implements java.io.Serializable {
  	// h屬性,保存我們傳遞進來的InvocationHandler
    protected InvocationHandler h;
    // 【有參構(gòu)造器】注意這里的參數(shù)
    protected Proxy(InvocationHandler h) {
      Objects.requireNonNull(h);
      this.h = h;
    }
    // 生成代理對象的方法
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
        throws IllegalArgumentException{
      	// 1、InvocationHandler強制不允許為空
        Objects.requireNonNull(h);
        // 獲取到目標接口
        final Class<?>[] intfs = interfaces.clone();
        /*
         * Look up or generate the designated proxy class.
         * 2、獲取到代理類的Class對象(也就是Proxy)
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         * 通過反射執(zhí)行 cl 的有參構(gòu)造,也就是下面這個,可以看到通過反射執(zhí)行Proxy有參構(gòu)造,
         * 將InvocationHandler賦值到了h屬性上
         */
        try {
            // 3、獲取到有參構(gòu)造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            // 4、通過構(gòu)造器來創(chuàng)建一個代理對象并返回,這里傳入的參數(shù)h 就是我們的【InvocationHandler】
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            // 省略....
        }
    }
}

5、問題總結(jié)

現(xiàn)在再來看上面拋出的三個問題!

為什么創(chuàng)建代理對象時需要傳入如下三個參數(shù):

  • 加載target類的類加載器
  • target類實現(xiàn)的接口
  • InvocationHandler

①、為什么要傳入類加載器

因為動態(tài)代理類也是類,我們普通的類是從【磁盤上的.class文件(也可以是其他地方,比如網(wǎng)絡(luò)上)】里加載而來,而動態(tài)代理類則是在【運行過程中動態(tài)生成的類】。

那么既然是類那么他就一定要【被類加載器加載后】才能被我們的【Java虛擬機】識別。

所以我們會傳入【加載target類的類加載器】,用該類加載器來加載【動態(tài)生成的代理類】

②、為什么要傳入target類實現(xiàn)的接口

為啥要傳入【target類實現(xiàn)的接口】呢?直接【繼承目標類】不行嗎?肯定不行

  • 從上面的【動態(tài)生成的代理類的結(jié)構(gòu)】來看,代理類繼承了Proxy類,由于【Java是單繼承】的,所以無法再通過繼承的方式來繼承【目標類】了。
  • 所以動態(tài)代理類需要【實現(xiàn)目標接口】,來重寫接口的方法

③、為什么要傳入InvocationHandler

  • 從【動態(tài)生成的代理類的結(jié)構(gòu)】可以看出,我們傳入的InvocationHandler最終會被作為一個屬性保存到Proxy對象的【h屬性】上
  • 并且【動態(tài)代理對象】會覆寫【目標接口的所有方法】,在方法中會使用 supper.h.invoke的方式調(diào)用InvocationHandler的invoke方法,所以我們需要傳入InvocationHandler
  • 動態(tài)代理的每個方法調(diào)用都會先走InvocationHandler.invoke()方法

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java可重入鎖的實現(xiàn)原理與應(yīng)用場景

    Java可重入鎖的實現(xiàn)原理與應(yīng)用場景

    今天小編就為大家分享一篇關(guān)于Java可重入鎖的實現(xiàn)原理與應(yīng)用場景,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • java數(shù)據(jù)結(jié)構(gòu)與算法之雙向循環(huán)隊列的數(shù)組實現(xiàn)方法

    java數(shù)據(jù)結(jié)構(gòu)與算法之雙向循環(huán)隊列的數(shù)組實現(xiàn)方法

    這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)與算法之雙向循環(huán)隊列的數(shù)組實現(xiàn)方法,結(jié)合實例形式分析了雙向循環(huán)隊列的原理與數(shù)組實現(xiàn)技巧,并附帶說明了該算法的用途,需要的朋友可以參考下
    2016-08-08
  • SpringBoot Shiro配置自定義密碼加密器代碼實例

    SpringBoot Shiro配置自定義密碼加密器代碼實例

    這篇文章主要介紹了SpringBoot Shiro配置自定義密碼加密器代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • 淺談JAVA 異常對于性能的影響

    淺談JAVA 異常對于性能的影響

    Java的異常處理為什么會影響性能?異常開銷很大。那么,這是不是就意味著您不該使用異常?當(dāng)然不是。但是,何時應(yīng)該使用異常,何時又不應(yīng)該使用異常呢?不幸的是,答案不是一下子就說得清楚的,我們來詳細探討下。
    2015-05-05
  • Java Swing組件BoxLayout布局用法示例

    Java Swing組件BoxLayout布局用法示例

    這篇文章主要介紹了Java Swing組件BoxLayout布局用法,結(jié)合實例形式分析了Swing使用BoxLayout容器進行布局的相關(guān)方法與操作技巧,需要的朋友可以參考下
    2017-11-11
  • 關(guān)于Java中的mysql時區(qū)問題詳解

    關(guān)于Java中的mysql時區(qū)問題詳解

    這篇文章主要給大家介紹了關(guān)于Java中mysql時區(qū)問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • Java 代理(Proxy)的原理及應(yīng)用

    Java 代理(Proxy)的原理及應(yīng)用

    動態(tài)代理技術(shù)就是用來產(chǎn)生一個對象的代理對象的。 我們在開發(fā)中之所以要產(chǎn)生一個對象的代理對象,主要用于攔截對真實業(yè)務(wù)對象的訪問。本文主要介紹了Java 代理的使用,感興趣的可以了解一下
    2021-05-05
  • Java使用easyExcel導(dǎo)出數(shù)據(jù)及單元格多張圖片

    Java使用easyExcel導(dǎo)出數(shù)據(jù)及單元格多張圖片

    除了平時簡單的數(shù)據(jù)導(dǎo)出需求外,我們也經(jīng)常會遇到一些有固定格式或者模板要求的數(shù)據(jù)導(dǎo)出,下面這篇文章主要給大家介紹了關(guān)于Java使用easyExcel導(dǎo)出數(shù)據(jù)及單元格多張圖片的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • MybatisPlus使用Mybatis的XML的動態(tài)SQL的功能實現(xiàn)多表查詢

    MybatisPlus使用Mybatis的XML的動態(tài)SQL的功能實現(xiàn)多表查詢

    本文主要介紹了MybatisPlus使用Mybatis的XML的動態(tài)SQL的功能實現(xiàn)多表查詢,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • 合成聚合復(fù)用原則_動力節(jié)點Java學(xué)院整理

    合成聚合復(fù)用原則_動力節(jié)點Java學(xué)院整理

    這篇文章主要介紹了合成聚合復(fù)用原則,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08

最新評論