以前已經用了5篇文章完整解釋了java動態代理的原理,本文將會爲這個系列補上最後一塊拼圖,展現java動態代理的使用方式和應用場景html
主要分爲如下4個部分java
1.爲何要使用java動態代理redis
2.如何使用java動態代理spring
3.框架中java動態代理的應用數據庫
4.java動態代理的基本原理設計模式
在設計模式中有一個很是經常使用的模式:代理模式。學術一些來說,就是爲某些對象的某種行爲提供一個代理對象,並由代理對象徹底控制該行爲的實際執行。數組
通俗來講,就是我想點份外賣,可是手機沒電了,因而我讓同窗用他手機幫我點外賣。在這個過程當中,其實就是我同窗(代理對象)幫我(被代理的對象)代理了點外賣(被代理的行爲),在這個過程當中,同窗能夠徹底控制點外賣的店鋪、使用的APP,甚至把外賣直接吃了都行(對行爲的徹底控制)。緩存
所以總結一下代理的4個要素:網絡
從實際編碼的角度來講,咱們假設遇到了這樣一個需求,須要記錄下一些方法的執行時間,因而最簡單的方式固然就是在方法的開頭記錄一個時間戳,在return以前記錄一個時間戳。但若是方法的流程很複雜,例如:框架
public class Executor { public void execute(int x, int y) { log.info("start:{}", System.nanoTime()); if (x == 3) { log.info("end:{}", System.nanoTime()); return; } for (int i = 0; i < 100; i++) { if (y == 5) { log.info("end:{}", System.nanoTime()); return; } } log.info("end:{}", System.nanoTime()); return; } }
咱們須要在每個return前都增長一行記錄時間戳的代碼,很麻煩。因而咱們想到能夠由方法的調用者來記錄時間,例如:
public class Invoker { private Executor executor = new Executor(); public void invoke() { log.info("start:{}", System.nanoTime()); executor.execute(1, 2); log.info("end:{}", System.nanoTime()); } }
咱們又遇到一個問題,若是該方法在不少地方調用,或者須要記錄的方法有多個,那麼依然會面臨重複手動寫log代碼的問題。
因而,咱們就能夠考慮建立一個代理對象,讓它負責幫咱們統一記錄時間戳,例如:
public class Proxy { Executor executor = new Executor(); public void execute(int x, int y) { log.info("start:{}", System.nanoTime()); executor.execute(x, y); log.info("start:{}", System.nanoTime()); } }
而在Invoker中,則由直接調用Executor中的方法改成調用Proxy的方法,固然方法的名字和簽名是徹底相同的。當其餘地方須要調用execute方法時,只須要調用Proxy中的execute方法,就會自動記錄下時間戳,而對於使用者來講是感知不到區別的。以下示例:
public class Invoker { private Proxy executor; public void invoke() { executor.execute(1, 2); } }
上面展現的代理,就是一個典型的靜態代理,「靜態」體如今代理方法是咱們直接編碼在類中的。
接着咱們就遇到了下一個問題,若是Executor新增了一個方法,一樣要記錄時間,那咱們就不得不修改Proxy的代碼。而且若是其餘類也有一樣的需求,那就須要新建不一樣的Proxy類才能較好的實現該功能,一樣很是麻煩。
那麼咱們就須要將靜態代理升級成爲動態代理了,而「動態」正是爲了優化前面提到的2個靜態代理遇到的問題。
建立java動態代理須要使用以下類
java.lang.reflect.Proxy
調用其newProxyInstance方法,例如咱們須要爲Map建立一個代理:
Map mapProxy = (Map) Proxy.newProxyInstance( HashMap.class.getClassLoader(), new Class[]{Map.class}, new InvocationHandler(){...} );
咱們接着就來分析這個方法。先查看其簽名:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
ClassLoader類型的loader:被代理的類的加載器,能夠認爲對應4要素中的被代理的對象。
Class數組的interfaces:被代理的接口,這裏其實對應的就是4要素中的被代理的行爲,能夠注意到,這裏須要傳入的是接口而不是某個具體的類,所以表示行爲。
InvocationHandler接口的h:代理的具體行爲,對應的是4要素中的行爲的徹底控制,固然也是java動態代理的核心。
最後返回的對象Object對應的是4要素中的代理對象。
接着咱們來示例用java動態代理來完成記錄方法執行時間戳的需求:
首先定義被代理的行爲,即接口:
public interface ExecutorInterface { void execute(int x, int y); }
接着定義被代理的對象,即實現了接口的類:
public class Executor implements ExecutorInterface { public void execute(int x, int y) { if (x == 3) { return; } for (int i = 0; i < 100; i++) { if (y == 5) { return; } } return; } }
接着是代理的核心,即行爲的控制,須要一個實現了InvocationHandler接口的類:
public class TimeLogHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } }
這個接口中的方法並不複雜,咱們仍是先分析其簽名
Object類型的proxy:最終生成的代理對象
Method類型的method:被代理的方法。這裏實際上是2個要素的複合,即被代理的對象是如何執行被代理的行爲的。由於雖然咱們說要對行爲徹底控制,但大部分時候,咱們只是對行爲增添一些額外的功能,所以依然是要利用被代理對象原先的執行過程的。
Object數組的args:方法執行的參數
由於咱們的目的是要記錄方法的執行的時間戳,而且原方法自己仍是依然要執行的,因此在TimeLogHandler的構造函數中,將一個原始對象傳入,method在調用invoke方法時便可使用。
定義代理的行爲以下:
public class TimeLogHandler implements InvocationHandler { private Object target; public TimeLogHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { log.info("start:{}", System.nanoTime()); Object result = method.invoke(target, args); log.info("end:{}", System.nanoTime()); return result; } }
接着咱們來看Invoker如何使用代理,這裏爲了方便演示咱們是在構造函數中實例化代理對象,在實際使用時能夠採用依賴注入或者單例等方式來實例化:
public class Invoker { private ExecutorInterface executor; public Invoker() { executor = (ExecutorInterface) Proxy.newProxyInstance( Executor.class.getClassLoader(), new Class[]{ExecutorInterface.class}, new TimeLogHandler(new Executor()) ); } public void invoke() { executor.execute(1, 2); } }
此時若是Exector新增了任何方法,那麼Invoker和TimeLogHandler將不須要任何改動就能夠支持新增方法的的時間戳記錄,有興趣的同窗能夠本身嘗試一下。
另外若是有其餘類也須要用到時間戳的記錄,那麼只須要和Executor同樣,經過Proxy.newProxyInstance方法建立便可,而不須要其餘的改動了。
接着咱們看一下java動態代理在如今的一些經常使用框架中的實際應用
spring aop是咱們spring項目中很是經常使用的功能。
例如咱們在獲取某個數據的時候須要先去redis中查詢是否已經有緩存了,若是沒有緩存再去讀取數據庫。咱們就能夠定義以下的一個切面和行爲,而後在須要該功能的方法上增長相應註解便可,而再也不須要每一個方法單獨寫邏輯了。以下示例:
@Aspect @Component public class TestAspect { /** * 表示全部有cn.tera.aop.RedisPoint註解的方法 * 都會執行先讀取Redis的行爲 */ @Pointcut("@annotation(cn.tera.aop.RedisPoint)") public void pointCut() { } /** * 實際獲取數的流程 */ @Around("pointCut()") public Object advise(ProceedingJoinPoint joinPoint) { try { /** * 先去查詢redis */ Object data = RedisUtility.get(some_key); if (data == null) { /** * joinPoint.proceed()表示執行原方法 * 若是redis中沒有緩存,那麼就去執行原方法獲取數據 * 而後塞入redis中,下次就能直接獲取到緩存了 */ data = joinPoint.proceed(); RedisUtility.put(some_key, data); } return data; } catch (Throwable r) { return null; } } }
而其背後的原理使用的就是java動態代理。固然這裏要求被註解的方法所在的類必須是實現了接口的(回想下Proxy.newProxyInstance方法的簽名),不然就須要使用另一個GCLib的庫了,不過這就是另一個故事了,這裏就不展開了。
Spring AOP中大部分狀況下都是給原執行邏輯添加一些東西。
在一些rpc框架中,客戶端只須要關注接口的的調用,而具體的遠程請求則由框架內部實現,例如咱們模擬一個簡單的rpc 請求,接口以下:
public interface OrderInterface { /** * 生成一張新訂單 */ void addOrder(); }
rpc框架能夠生成接口的代理對象,例如:
public class SimpleRpcFrame { /** * 建立一個遠程請求代理對象 */ public static <T> T getRemoteProxy(Class<T> service) { return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, new RpcHandler(service)); } /** * 處理具體遠程調用的類 */ static class RpcHandler implements InvocationHandler { private Class service; public RpcHandler(Class service) { this.service = service; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 根據接口名和方法名,發起一次特定的網絡請求,獲取數據 */ Object result = RemoteCallUtility.request(service.getName(), method.getName(), args); return result; } } }
而客戶端調用的時候不須要接口的具體實現,只須要經過rpc框架獲取接口的代理便可,此時到底是採用http協議或者直接經過socket請求數據都交由框架負責了,例如:
public class RpcInvoker { public void invoke() { OrderInterface order = SimpleRpcFrame.getRemoteProxy(OrderInterface.class); order.addOrder(); } }
RPC中的代理則是徹底不須要原執行邏輯,而是徹底地控制了行爲的執行過程
那麼框架使用java動態代理的示例就介紹到此。
以前我已經經過5篇文章完整介紹了java動態代理的實現原理,不過由於實在有些晦澀,因此這裏我拋棄細節代碼的解析,使得你們儘可能從直覺的角度來理解其基本原理。
假設咱們仍是實現一開始的添加時間戳的功能,此時,咱們須要以下代碼獲取其代理:
ExecutorInterface executor = (ExecutorInterface) Proxy.newProxyInstance( Executor.class.getClassLoader(), new Class[]{ExecutorInterface.class}, new TimeLogHandler() ); executor.execute(1, 2);
此時,咱們打印一下executor的實際類名、所實現的接口和父類的名稱,獲得結果以下:
類名:com.sun.proxy.$Proxy11 父類:java.lang.reflect.Proxy 實現接口:ExecutorInterface
所以,生成的代理類有以下3個特色:
1.繼承了Proxy類
2.實現了咱們傳入的接口
3.以$Proxy+隨機數字的命名
接着咱們仍是須要略微查看一下newProxyInstance方法的源碼,只須要關心下面幾行核心代碼,以下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ... /** * 根據咱們傳進來的接口建立一個類 */ Class<?> cl = getProxyClass0(loader, intfs); ... /** * 找到類的構造方法,該構造方法獲取一個InvocationHandler類型的參數 */ final Constructor<?> cons = cl.getConstructor(constructorParams); ... /** * 經過構造方法生成代理類的實例 */ return cons.newInstance(new Object[]{h}); }
所以總結一下動態代理對象建立的過程
1.根據咱們傳入的接口動態地建立一個Class
2.獲取類的構造函數
3.將InvocationHandler做爲參數傳入構造函數,實例化代理對象的實例,並將其返回
固然,這裏最核心的方法天然是類的建立,簡而言之,就是在運行時,一個字節一個字節地構造一個字節數組,而這個字節數組正是一個.class字節碼,而後經過一個native方法,將其轉化爲咱們運行時的Class類。
再通俗一些來講:平時咱們使用的類都是預先編譯好的.class文件,而動態代理則是直接在運行時經過組裝一個byte數組的方式建立一個.class文件,這樣應該就是比較好理解了吧。
若是對這個byte數組是如何構建的有興趣,那麼歡迎看一下我以前寫的5篇文章,裏面不只介紹了動態代理的源碼,還能深刻了解一下class字節碼更細節的結構
1.https://www.cnblogs.com/tera/p/13267630.html
2.https://www.cnblogs.com/tera/p/13280547.html
3.https://www.cnblogs.com/tera/p/13336627.html
4.https://www.cnblogs.com/tera/p/13419025.html
5.https://www.cnblogs.com/tera/p/13442018.html
最後,咱們來看一下這個生成出來的代理類究竟長啥樣,正符合咱們以前總結出的代理對象的3個特色(在以前的文章中也有展現如何看到該內容)。特別注意的是由於全部的類都是繼承自Object,所以除了咱們本身接口中定義的方法,還會有Object類的種的方法:
public final class $Proxy11 extends Proxy implements ExecutorInterface { private static Method m1; private static Method m2; private static Method m0; private static Method m3; public $Proxy11(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); } } 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); } } public final void execute(int var1, int var2) throws { try { super.h.invoke(this, m3, new Object[]{var1, var2}); } catch (RuntimeException | Error var4) { throw var4; } catch (Throwable var5) { throw new UndeclaredThrowableException(var5); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("cn.tera.aopproxy.proxyuse.ExecutorInterface").getMethod("execute", Integer.TYPE, Integer.TYPE); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
到此,java動態代理的基本介紹就結束了
最後咱們總結一下java動態代理的思想和原理
1.代理的4要素:代理對象、被代理的行爲、被代理的對象、行爲的徹底控制
2.代理的應用:方便地爲某些行爲添加一些共同的邏輯(Spring AOP)或者是將行爲的執行徹底交由代理控制(RPC)
3.java動態代理的原理:在運行時構建一個class字節碼數組,並將其轉換成一個運行時的Class對象,而後構造其實例