溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

基于JDK動態(tài)代理原理是什么

發(fā)布時間:2023-05-05 10:55:15 來源:億速云 閱讀:88 作者:zzz 欄目:開發(fā)技術

這篇文章主要介紹了基于JDK動態(tài)代理原理是什么的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇基于JDK動態(tài)代理原理是什么文章都會有所收獲,下面我們一起來看看吧。

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

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

    • 加載target類的類加載器

    • target類實現(xiàn)的接口

    • InvocationHandler

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

    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)接口相關方法

    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對象生產代理對象

    • 該工廠類借助Proxy.newProxyInstance來為目標對象創(chuàng)建代理對象

    public class JdkDynamicProxyFactory {
        /**
         * 創(chuàng)建target類的代理對象
         * 注意:當調用代理對象中的方法時,其實就是調用的InvocationHandler里面的invoke方法,然后在invoke方法里調用目標對象對應的方法
         *
         * @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í)行結果
                            return result;
                        }
                    });
            // 返回代理對象
            return (T) proxyInstance;
        }
    }

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

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

    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);
            // 調用代理對象的方法
            proxy.doSomething();
            System.out.println("=====================華麗的分割線=====================");
            proxy.sayHello("wenpan");
        }
    }

    3、分析生成的動態(tài)代理類的結構

    在上面一步中我們使用

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

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

    • 可以看到代理類【繼承了Proxy類】,并且【實現(xiàn)了目標接口Subject】,覆寫了接口中的【每一個方法】

    • 這也說明了為什么JDK代理需要實現(xiàn)接口,因為Java是單繼承的,既然代理類繼承了Proxy類那么就無法再繼承其他類了

    • 在代理類中將我們的【目標接口Subject】的【所有方法(包括object父類的方法)】都以【靜態(tài)屬性的形式】保存了起來(主要是為了方便后面的【反射調用】)

    • 在調用動態(tài)代理對象的某個方法時(比如:調用doSomething方法),實質上是調用的【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類,調用【Proxy類】的【h屬性】的invoke方法執(zhí)行
              	// 重點:【注意這里的super.h】其實就是我們創(chuàng)建代理對象是傳入的【InvocationHandler】,不信往后面看
              	// 這里也就體現(xiàn)了創(chuàng)建代理對象時為什么需要傳入【InvocationHandler】,以及為什么調用代理對象的方法時都是執(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 {
              	// 調用了【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 {
              	// 調用了【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í)行代理對象的方法的時候其實質是調用的【super.h.invoke方法】也就是【Proxy.h.invoke方法】,那么我們仔細看看【Proxy.h】到底是什么鬼

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

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

    • 通過下面代碼我們就明確了使用【newProxyInstance】方法創(chuàng)建代理對象時所做的幾件事情

    • 首先通過【目標接口】 + 【類加載器】創(chuàng)建一個Proxy類的【Class對象】

    • 然后通過這個【Class對象】獲取到他的有參構造器,并且傳入我們自定義的【InvocationHandler】作為構造函數參數,并且通過反射的方式調用有參構造器創(chuàng)建一個【Proxy對象】

    • 在【Proxy的有參構造器】中,會將傳入的【InvocationHandler】保存到 【h屬性】上(方便后面的supper.h.invoke調用)

    • 代理對象創(chuàng)建完畢

    public class Proxy implements java.io.Serializable {
      	// h屬性,保存我們傳遞進來的InvocationHandler
        protected InvocationHandler h;
        // 【有參構造器】注意這里的參數
        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 的有參構造,也就是下面這個,可以看到通過反射執(zhí)行Proxy有參構造,
             * 將InvocationHandler賦值到了h屬性上
             */
            try {
                // 3、獲取到有參構造器
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                // 4、通過構造器來創(chuàng)建一個代理對象并返回,這里傳入的參數h 就是我們的【InvocationHandler】
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                // 省略....
            }
        }
    }

    5、問題總結

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

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

    • 加載target類的類加載器

    • target類實現(xiàn)的接口

    • InvocationHandler

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

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

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

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

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

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

    • 從上面的【動態(tài)生成的代理類的結構】來看,代理類繼承了Proxy類,由于【Java是單繼承】的,所以無法再通過繼承的方式來繼承【目標類】了。

    • 所以動態(tài)代理類需要【實現(xiàn)目標接口】,來重寫接口的方法

    ③、為什么要傳入InvocationHandler

    • 從【動態(tài)生成的代理類的結構】可以看出,我們傳入的InvocationHandler最終會被作為一個屬性保存到Proxy對象的【h屬性】上

    • 并且【動態(tài)代理對象】會覆寫【目標接口的所有方法】,在方法中會使用 supper.h.invoke的方式調用InvocationHandler的invoke方法,所以我們需要傳入InvocationHandler

    • 動態(tài)代理的每個方法調用都會先走InvocationHandler.invoke()方法

    關于“基于JDK動態(tài)代理原理是什么”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“基于JDK動態(tài)代理原理是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業(yè)資訊頻道。

    向AI問一下細節(jié)

    免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    jdk
    AI