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動態代理