【JAVA】JDK動態代理初探之Proxy.newProxyInstance

JDK的動態代理這個詞大佬們經常掛在嘴邊,可我卻是雲裏霧裏。
這幾個字分開倒是都認識,組合在一起就不明白它是表達的什麼意思了

talk is cheap, show me the code.

基礎實戰

main入口類

  • 新建一個含有main方法的測試類TestMain.java

    package com.itplh.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    /**
     * JDK動態代理
     * Proxy 代理的本質是方法的攔截
     *
     * $Proxy0 extends Proxy implements Student,我們看到代理類繼承了Proxy類,
     * 所以也就決定了java動態代理只能對接口進行代理,Java的繼承機制註定了這些動態代理類們無法實現對class的動態代理
     *
     * 因爲JDK的動態代理只能基於接口,所以 newProxyInstance 的第二個參數 Class<?>[] interfaces 的元素必須是接口
     *
     *
     * @author: tanpeng
     * @since: 2020-06-02 9:57
     */
    public class TestMain {
        public static void main(String[] args) {
            // 使用匿名內部類的方式聲明一個Teacher接口的實現類
            Student student = () -> "I'm Student interface of implements class.";
    
            // 一、Proxy.newProxyInstance + 匿名內部類
            Student studentProxy1 = (Student) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{Student.class},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            print("Proxy.newProxyInstance + 匿名內部類",
                                    (String) method.invoke(student, args),
                                    method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
                            return null;
                        }
                    });
            studentProxy1.hi();
    
            // 二、Proxy.newProxyInstance + lambda表達式
            Student studentProxy2 = (Student) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{Student.class},
                    (proxy, method, args1) -> {
                        print("Proxy.newProxyInstance + lambda表達式.",
                                (String) method.invoke(student, args1),
                                method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
                        return null;
                    });
            studentProxy2.hi();
    
            // 三、Proxy.newProxyInstance + InvocationHandler實現類
            Student studentProxy3 = (Student) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{Student.class},
                    new TestInvocationHandler<Student>(student));
            studentProxy3.hi();
    
            // 四、封裝ProxyFactory 代理Student接口
            Student studentProxy4 = TestProxyFactory.getProxyObject(student);
            studentProxy4.hi();
    
            // 五、封裝ProxyFactory 代理Teacher接口
            Teacher teacher = () -> "I'm teacher.";
            Teacher teacherProxy = TestProxyFactory.getProxyObject(teacher);
            teacherProxy.hi();
    
            // 這裏傳實現類 ZhangSan.class 將會報錯
            // Exception in thread "main" java.lang.IllegalArgumentException: com.itplh.proxy.ZhangSan is not an interface
    //        Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
    //                new Class[]{ZhangSan.class},
    //                new TestInvocationHandler<Student>(student));
        }
    
        public static void print(String... text) {
            System.out.println("------------------------------------------");
            Arrays.asList(text).forEach(t -> System.out.println("------ " + t));
            System.out.println("------------------------------------------");
            System.out.println();
        }
    }
    
    interface Student {
        @TestAnnotation("select * from student where id = #{id}")
        String hi();
    }
    
    class ZhangSan implements Student {
        @Override
        public String hi() {
            return "I'm zhangsan.";
        }
    }
    
    interface Teacher {
        @TestAnnotation("select * from teacher where id = #{id}")
        String hi();
    }
    
  • 控制檯輸出

    ------------------------------------------
    ------ Proxy.newProxyInstance + 匿名內部類
    ------ I'm Student interface of implements class.
    ------ SELECT * FROM STUDENT WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ Proxy.newProxyInstance + lambda表達式.
    ------ I'm Student interface of implements class.
    ------ SELECT * FROM STUDENT WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ InvocationHandler of implements class.
    ------ I'm Student interface of implements class.
    ------ SELECT * FROM STUDENT WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.itplh.proxy.Student.
    ------ I'm Student interface of implements class.
    ------ SELECT * FROM STUDENT WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.itplh.proxy.Teacher.
    ------ I'm Teacher interface of implements class.
    ------ SELECT * FROM TEACHER WHERE ID = #{ID}
    ------------------------------------------
    

其他類

  • 自定義一個註解TestAnnotation.java

    package com.itplh.proxy;
    
    import java.lang.annotation.*;
    
    /**
    * @author: tanpeng
    * @since: 2020-06-02 10:09
    */
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface TestAnnotation {
       String[] value();
    }
    
  • 自定義TestInvocationHandler.java

    package com.itplh.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * 動態代理-攔截器
     * 作用:控制目標對象的目標方法的執行
     * 
     * @author: tanpeng
     * @since: 2020-06-02 9:57
     */
    public class TestInvocationHandler<T> implements InvocationHandler {
    
        // InvocationHandler持有的被代理對象
        private T target;
    
        public TestInvocationHandler(T target) {
            this.target = target;
        }
    
        /**
         * 調用被代理的對象的方法都會進入本方法
         *
         * @param proxy  代表動態代理對象
         * @param method 代表正在執行的方法
         * @param args   代表調用目標方法時傳入的實參
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            TestMain.print("InvocationHandler of implements class.",
                    (String) method.invoke(target, args),
                    method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
            return null;
        }
    }
    
  • 封裝一個代理工廠TestProxyFactory.java

    package com.itplh.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    /**
     * @author: tanpeng
     * @since: 2020-06-02 11:08
     */
    public class TestProxyFactory {
    
        /**
         * static <T> 表示將該方法聲明爲泛型方法
         *
         * @param target 目標代理對象
         * @return
         */
        public static <T> T getProxyObject(T target) {
            return getProxyObject(target, (proxy, method, args) -> {
                TestMain.print("ProxyFactory " + target.getClass().getInterfaces()[0].getName() + ".",
                        (String) method.invoke(target, args),
                        method.getAnnotation(TestAnnotation.class).value()[0].toUpperCase());
                return null;
            });
        }
    
        /**
         * static<T> 表示將該方法聲明爲泛型方法
         *
         * @param target 目標代理對象
         * @param invocationHandler
         * @return
         */
        public static <T> T getProxyObject(T target, InvocationHandler invocationHandler) {
            return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    target.getClass().getInterfaces(),
                    invocationHandler);
        }
    }
    

測試含有多個方法的接口

  • 新建接口People 、User,及實現類DefaultUser

    interface People {
        @TestAnnotation("String hi();")
        String hi();
    }
    
    interface User extends People {
        @TestAnnotation("select username from user where id = #{id}")
        String selectUsernameById(String id);
    
        @TestAnnotation("select email from user where id = #{id}")
        String selectEmailById(String id);
    }
    
    class DefaultUser implements User {
        @Override
        public String hi() {
            return "This is DefaultUser#hi.";
        }
        
        @Override
        public String selectUsernameById(String id) {
            return "tanpeng";
        }
        
    	@Override
        public String selectEmailById(String id) {
            return "[email protected]";
        }
    }
    
  • main 函數中測試

    DefaultUser user = new DefaultUser();
    
    User userProxy = TestProxyFactory.getProxyObject(user);
    userProxy.hi();
    userProxy.selectUsernameById("1");
    userProxy.selectEmailById("1");
    
    People peopleProxy = TestProxyFactory.getProxyObject(user);
    peopleProxy.hi();
    ((User) peopleProxy).selectUsernameById("1");
    ((User) peopleProxy).selectEmailById("1");
    
  • 控制檯輸出

    ------------------------------------------
    ------ ProxyFactory com.aden.modules.datax.controller.User.
    ------ This is DefaultUser#hi.
    ------ STRING HI();
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.aden.modules.datax.controller.User.
    ------ tanpeng
    ------ SELECT USERNAME FROM USER WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.aden.modules.datax.controller.User.
    ------ tanpeng@itplh.com
    ------ SELECT EMAIL FROM USER WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.aden.modules.datax.controller.User.
    ------ This is DefaultUser#hi.
    ------ STRING HI();
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.aden.modules.datax.controller.User.
    ------ tanpeng
    ------ SELECT USERNAME FROM USER WHERE ID = #{ID}
    ------------------------------------------
    
    ------------------------------------------
    ------ ProxyFactory com.aden.modules.datax.controller.User.
    ------ tanpeng@itplh.com
    ------ SELECT EMAIL FROM USER WHERE ID = #{ID}
    ------------------------------------------
    

應用場景

  • 通過代理對類進行增強
    設計模式中有一個設計原則是開閉原則,是說對修改關閉對擴展開放。
    我們在工作中有時會接手很多前人的代碼,裏面代碼邏輯讓人摸不着頭腦(sometimes the code is really like shit)。
    這時就很難去下手修改代碼,那麼這時我們就可以通過代理對類進行增強。
  • AOP
  • 鬆散耦合
    在使用RPC框架的時候,框架本身並不能提前知道各個業務方要調用哪些接口的哪些方法 。
    那麼這個時候,就可用通過動態代理的方式來建立一箇中間人給客戶端使用,
    也方便框架進行搭建邏輯,某種程度上也是客戶端代碼和框架鬆耦合的一種表現。

知識拓展

  • 靜態代理
    在代碼編譯時就確定了被代理的類是哪一個

  • 動態代理
    在代碼運行期間才加載被代理的類,編譯時並未確定被代理的類是哪一個

  • 靜態代理動態代理的區別
    代理涉及到兩個關聯詞代理類委託類
    靜態代理 一個代理類針對一個委託類,即一對一
    動態代理 一個代理類可利用反射機制代理多個委託類,即一對多

  • 兩種動態代理的區別

    • JDK的動態代理 基於接口的代理

      • 代理對象和目標對象實現了共同的接口
      • 攔截器必須實現InvocationHanlder接口
    • cglib的動態代理 基於類的代理 (Code Generation Library )

      • 代理對象是目標對象的子類
      • 攔截器必須實現MethodInterceptor接口
  • 動態代理的相關問題

    • 代理對象是由誰產生的
      JVM產生的,不像靜態代理,我們自己得new個代理對象出來。

    • 代理對象實現了什麼接口
      實現的接口是目標對象實現的接口。同靜態代理中代理對象實現的接口。那個繼承關係圖還是相同的。
      代理對象目標對象都實現一個共同的接口。所以Proxy.newProxyInstance()方法返回的類型就是這個接口類型。

    • 代理對象的方法體是什麼
      代理對象的方法體中的內容就是攔截器中invoke方法中的內容。
      所有代理對象的處理邏輯,控制是否執行目標對象的目標方法。都是在invoke方法裏面處理的。

    • 攔截器中的invoke方法中的method參數是在什麼時候賦值的
      代理對象調用目標方法的時候。
      如,studentProxy1.hi();userProxy.selectUsernameById("1");
      這時實際上進入的是攔截器中的invoke方法,這個時候攔截器中的invoke方法中的method參數會被賦值。

動態代理對象時用JDK的相關代碼生成的,叫JDK動態代理
動態代理對象時用cglib的 jar 包,叫cglib動態代理

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章