摘要:動態代理深度解析引言說起動態代理,很多人可能都沒有直接去使用過。因為的動態代理只能代理接口,而不能代理原始的類。接下來是真正壓軸的環節,實現自己的動態代理類。
Java動態代理深度解析 引言
說起動態代理,很多人可能都沒有直接去使用過。但是只要用過Spring,那動態代理就是一個是個繞不過的坎,因為Spring的核心特性之一AOP就是基于動態代理來實現的,那么什么情況下需要用到動態代理呢?
場景考慮這樣一個教師的接口:
public interface Teacher { void teach(); }
假設我們有一個TeacherChan的實現類,陳老師教的是攝影:
public class TeacherChan implements Teacher { @Override public void teach() { System.out.println("大家好,我是陳老師,我教大家攝影!"); } }
另外還有一個TeacherCang的實現類,蒼老師教的是生物:
public class TeacherCang implements Teacher { @Override public void teach() { System.out.println("大家好,我是蒼老師,我教大家生物!"); } }
不管是陳老師還是蒼老師,只要實現了Teacher這個接口,給我們傳道授業解惑,為了禮貌起見,我們總應該給人家問聲好吧。而問好這件事不需要老師主動要求,可以交給代理來做,每次有老師來上課,代理自動做了問好這件事。而代理類又分為靜態代理和動態代理,靜態代理在編寫代碼時已經確定了要代理的類,只能代理單一的類型,在此略過,今天重點講動態代理。
Java動態代理Java動態代理創建代理類的方法為:
Proxy.newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h)
其中ClassLoader是用來定義代理類的class文件的,用系統默認的就好,interfaces是要代理的接口,InvocationHandler是用來實際執行代理方法的接口,常用做法是實現該接口,并將需要代理的類實例對象傳進去。
實現自己的方法執行器:
public class JdkDynamicProxy implements InvocationHandler { private Object proxied; public JdkDynamicProxy(Object object) { this.proxied = object; } /** * proxy為創建的代理類實例,method是本次被代理的方法,args是方法的參數 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-------------------老師好[by jdk動態代理]-------------------"); // 執行代理方法 Object obj = method.invoke(proxied, args); System.out.println("-------------------老師再見[by jdk動態代理]-------------------"); // 返回方法執行結果 return obj; } }
創建代理類對象并執行:
// 代理陳老師 Teacher proxy1 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(), new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherChan())); proxy1.teach(); // 代理蒼老師 Teacher proxy2 = (Teacher) Proxy.newProxyInstance(Teacher.class.getClassLoader(), new Class[]{Teacher.class}, new JdkDynamicProxy(new TeacherCang())); proxy2.teach();
輸出結果:
-------------------老師好[by jdk動態代理]------------------- 大家好,我是陳老師,我教大家攝影! -------------------老師再見[by jdk動態代理]------------------- -------------------老師好[by jdk動態代理]------------------- 大家好,我是蒼老師,我教大家生物! -------------------老師再見[by jdk動態代理]-------------------
實際上,Java會過濾掉接口所有final、native等方法,并為剩下的所有符合條件的方法生成代理方法。而且,熟悉Spring的朋友應該知道,Spring的AOP機制的實現不僅使用了Java的動態代理,而且還引入了CGLib。因為Java的動態代理只能代理接口,而不能代理原始的類。那么為什么Java不能代理類呢,答案是Java的單繼承機制。
深入Java動態代理的實現Java的動態代理是怎么實現的呢?其實很簡單,就是運行時生成一個代理類,該類實現了需要代理的接口,并返回這個代理類的實例對象給調用者。調試進入Proxy.newProxyInstance()的方法內部,可以看到在Proxy內部生成class字節碼的方法:
// 生成的代理類名前綴 private static final String proxyClassNamePrefix = "$Proxy"; // 生成的代理類名序號 private static final AtomicLong nextUniqueNumber = new AtomicLong(); // 序號值加1 long num = nextUniqueNumber.getAndIncrement(); // 代理類名:$ProxyN String proxyName = proxyPkg + proxyClassNamePrefix + num; ... // 生成代理類的class字節碼 byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
不難看出,生成的代理類的類名是$ProxyN的形式,所以我們經常會看到$Proxy0這個類,就是動態代理在運行時生成的。
既然知道了動態代理是怎么生成代理類的了,那我們不妨把它生成的類打印出來看看,到底里面是怎么實現的。
// 調用Java生成字節碼文件的方法 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( "com.test.$proxy0.class", new Class[]{Teacher.class}, Modifier.FINAL); // 輸出文件到本地 FileOutputStream out = new FileOutputStream(new File("/temp/$Proxy0.class")); out.write(proxyClassFile); out.flush(); out.close();
用java反編譯軟件打開生成的$Proxy0.class文件,內容如下:
package com.test.$proxy0; import com.demos.java.basedemo.proxy.bean.Teacher; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; final class class extends Proxy implements Teacher { private static Method m1; private static Method m2; private static Method m3; private static Method m0; public class(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } public final boolean equals(Object paramObject) throws { try { return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() throws { try { return (String)this.h.invoke(this, m2, null); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void teach() throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() throws { try { return ((Integer)this.h.invoke(this, m0, null)).intValue(); } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") }); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.demos.java.basedemo.proxy.bean.Teacher").getMethod("teach", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } }
可以看出,代理類不僅代理了teach()這個方法,還代理了toString()、equals()和hashCode()方法,因為java中所有的類都是繼承Object類的,所以自然有這些方法。其中還有一個地方要特別注意:生成的代理類繼承了Proxy這個類,因此它只能通過實現接口來代理其他的類,現在知道為什么Java動態代理只能代理接口了。
既然都講到這個份上了,當然不能不繼續深入了。接下來是真正壓軸的環節,實現自己的動態代理類。
手動實現動態代理首先分析一下實現動態代理需要的步驟:
1.拼接java類實現,并生成class字節碼 2.加載class字節碼到JVM 3.實現自己的InvocationHandler處理器 4.對外提供接口
既然知道了Java動態代理的原理,我們不妨借鑒Java生成的class文件格式,同時去掉默認繼承的Proxy,使得我們自己的動態代理既可以代理接口,也可以代理類。先寫出代理類的格式(假設代理的是類TeacherChan):
public class $Proxy0 extends TeacherChan { private InvocationHandler handler; private static Method m0; static { try { // 利用反射獲取TeacherChan的teach()方法 m0 = TeacherChan.class.getMethod("teach", new Class[]{}); } catch (NoSuchMethodException ne) { throw new NoSuchMethodError(ne.getMessage()); } } // 構造方法中傳入代理類處理器 public $Proxy0(InvocationHandler handler) { this.handler = handler; } public void teach() { try { // 收集teach()方法傳入的參數,此處參數為空 Object[] args = new Object[]{}; // 執行代理類的teach() Object result = handler.invoke(this, m0, args); // 如果有返回值,此處要返回result } catch (Error|RuntimeException e) { throw e; } catch (Throwable t) { throw new UndeclaredThrowableException(t); } } }
好了,生成類的格式大概就是這樣設計,實際編寫代碼時需要處理參數、返回值和異常的情況,略微有點繁瑣。下面是動態類生成器:
public class MyProxyGenerator { // 換行符 public static final String LINE_SEPARATOR = " "; // 動態代理類包名 public static final String PROXY_CLASS_PACKAGE = "com.demos.proxy"; // 動態代理類名前綴 public static final String PROXY_CLASS_NAME_PREFIX = "$Proxy"; // 動態代理類文件索引 public static final AtomicLong INDEX_GENERATOR = new AtomicLong(); // 動態代理生成文件臨時目錄 public static final String PROXY_CLASS_FILE_PATH = "/temp"; /** * 生成代理類并加載到JVM * @param clazz * @param methods * @throws Exception */ public static Class> generateAndLoadProxyClass(Class> clazz, Method[] methods) throws Exception { long index = INDEX_GENERATOR.getAndIncrement(); // 代理類類名 String className = PROXY_CLASS_NAME_PREFIX + index; String fileName = PROXY_CLASS_FILE_PATH + File.separator + className + ".java"; FileWriter writer = null; try { // 生成.java文件 writer = new FileWriter(new File(fileName)); writer.write(generateClassCode(PROXY_CLASS_PACKAGE, className, clazz, methods)); writer.flush(); // 編譯.java文件 compileJavaFile(fileName); // 加載class到JVM String classPath = PROXY_CLASS_FILE_PATH + File.separator + className + ".class"; Class> proxyClass = MyClassLoader.getInstance().findClass(classPath, PROXY_CLASS_PACKAGE + "." + className); return proxyClass; } finally { if (writer != null) { writer.close(); } } } /** * 編譯.java文件 * @param fileName * @throws IOException */ private static void compileJavaFile(String fileName) throws IOException { compileByTools(fileName); // compileByExec(fileName); } /** * 使用Runtime執行javac命令 * 注意: 需要指定classpath, 否則找不到依賴的類 * 建議使用compileByTools() * @param fileName * @throws IOException */ @Deprecated private static void compileByExec(String fileName) throws IOException { // 獲取當前的classpath String classpath = MyProxyGenerator.class.getResource("/").getPath(); // 運行命令: javac -classpath ${classpath} ${filepath} String command = "javac -classpath " + classpath + " " + fileName; Process process = Runtime.getRuntime().exec(command); // 等待執行, 并輸出錯誤日志 try { InputStream errorStream = process.getErrorStream(); InputStreamReader inputStreamReader = new InputStreamReader(errorStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String line = null; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } int exitVal = process.waitFor(); System.out.println("Process exitValue: " + exitVal); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 使用JDK自帶的JavaCompiler * @param fileName * @throws IOException */ private static void compileByTools(String fileName) throws IOException { // 獲取系統Java編譯器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 獲取標準文件管理器實例 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); try { Iterable units = fileManager.getJavaFileObjects(fileName); JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units); task.call(); } finally { fileManager.close(); } } /** * 拼接 class 代碼片段 * @param packageName 代理類包名 * @param clazz 要代理的類型 * @return */ private static String generateClassCode(String packageName, String className, Class> clazz, Method[] methods) throws Exception { StringBuilder classCodes = new StringBuilder(); /*--------------------包名和依賴 start--------------------*/ classCodes.append("package ").append(packageName).append(";").append(LINE_SEPARATOR); classCodes.append(LINE_SEPARATOR); classCodes.append("import java.lang.reflect.*;").append(LINE_SEPARATOR); classCodes.append(LINE_SEPARATOR); /*--------------------包名和依賴 start--------------------*/ /*--------------------類定義 start--------------------*/ classCodes.append("public class ").append(className); if (clazz.isInterface()) { classCodes.append(" implements "); } else { classCodes.append(" extends "); } classCodes.append(clazz.getName()).append(" {").append(LINE_SEPARATOR); classCodes.append(LINE_SEPARATOR); /*--------------------類定義 end--------------------*/ /*--------------------聲明變量InvocationHandler start--------------------*/ classCodes.append("private InvocationHandler handler;").append(LINE_SEPARATOR); classCodes.append(LINE_SEPARATOR); /*--------------------聲明變量InvocationHandler end--------------------*/ /*--------------------聲明代理方法 start--------------------*/ for (int i = 0; i < methods.length; i++) { classCodes.append("private static Method m").append(i).append(";").append(LINE_SEPARATOR); } classCodes.append(LINE_SEPARATOR); /*--------------------聲明代理方法 end--------------------*/ /*--------------------代理方法對象初始化 start--------------------*/ classCodes.append("static {").append(LINE_SEPARATOR); classCodes.append(" ").append("try {").append(LINE_SEPARATOR); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; classCodes.append(" ").append(" ").append("m").append(i).append(" = ").append(clazz.getName()) .append(".class.getMethod("").append(method.getName()).append("", new Class[]{"); // 方法參數 Parameter[] params = method.getParameters(); if (params.length != 0) { for (int j = 0; j < params.length; j++) { if (j != 0) { classCodes.append(", "); } Parameter param = params[j]; classCodes.append(param.getType().getName()).append(".class"); } } classCodes.append("});").append(LINE_SEPARATOR); } classCodes.append(" ").append("} catch (NoSuchMethodException ne) {").append(LINE_SEPARATOR); classCodes.append(" ").append(" ").append("throw new NoSuchMethodError(ne.getMessage());").append(LINE_SEPARATOR); classCodes.append(" ").append("}").append(LINE_SEPARATOR); classCodes.append("}").append(LINE_SEPARATOR); classCodes.append(LINE_SEPARATOR); /*--------------------代理方法對象初始化 end--------------------*/ /*--------------------定義構造函數 start--------------------*/ classCodes.append("public ").append(className).append("(InvocationHandler handler) {").append(LINE_SEPARATOR); classCodes.append(" ").append("this.handler = handler;").append(LINE_SEPARATOR); classCodes.append("}").append(LINE_SEPARATOR); classCodes.append(LINE_SEPARATOR); /*--------------------定義構造函數 end--------------------*/ /*--------------------填充其他函數 start--------------------*/ classCodes.append(generateMethodCode(clazz, methods)); /*--------------------填充其他函數 end--------------------*/ // 類結束 classCodes.append("}").append(LINE_SEPARATOR); return classCodes.toString(); } /** * 拼接 method 代碼片段 * @param clazz * @param methods * @return * @throws Exception */ private static String generateMethodCode(Class> clazz, Method[] methods) throws Exception { StringBuilder methodCodes = new StringBuilder(); for (int i = 0; i < methods.length; i++) { Method method = methods[i]; // 返回類型 String returnType = method.getReturnType().getName(); // 參數列表 Parameter[] params = method.getParameters(); // 異常列表 Class>[] exceptionTypes = method.getExceptionTypes(); /*--------------------方法定義 start--------------------*/ methodCodes.append("public ").append(returnType).append(" ").append(method.getName()); methodCodes.append("("); // 填充參數 if (params.length != 0) { for (int j = 0; j < params.length; j++) { if (j != 0) { methodCodes.append(", "); } Parameter param = params[j]; methodCodes.append(param.getType().getName()).append(" ").append(param.getName()); } } methodCodes.append(")"); // 填充異常 if (exceptionTypes.length != 0) { methodCodes.append(" throws "); for (int j = 0; j < exceptionTypes.length; j++) { if (j != 0) { methodCodes.append(", "); } methodCodes.append(exceptionTypes[j].getName()); } } methodCodes.append(" {").append(LINE_SEPARATOR); /*--------------------方法定義 end--------------------*/ /*--------------------方法體 start--------------------*/ methodCodes.append(" ").append("try {").append(LINE_SEPARATOR); // 方法參數 methodCodes.append(" ").append(" ").append("Object[] args = new Object[]{"); if (params.length != 0) { for (int j = 0; j < params.length; j++) { if (j != 0) { methodCodes.append(", "); } Parameter param = params[j]; methodCodes.append(param.getName()); } } methodCodes.append("};").append(LINE_SEPARATOR); // 執行InvocationHandler.invoke() methodCodes.append(" ").append(" ").append("Object result = handler.invoke(this, m").append(i) .append(", args);").append(LINE_SEPARATOR); // 返回結果 if (!"void".equals(returnType)) { methodCodes.append(" ").append(" ").append("return (").append(returnType).append(") result;").append(LINE_SEPARATOR); } // 異常處理 methodCodes.append(" ").append("} catch (Error|RuntimeException"); for (Class> exceptionType : exceptionTypes) { methodCodes.append("|").append(exceptionType.getName()); } methodCodes.append(" e) {").append(LINE_SEPARATOR); methodCodes.append(" ").append(" ").append("throw e;").append(LINE_SEPARATOR); methodCodes.append(" ").append("} catch (Throwable t) {").append(LINE_SEPARATOR); methodCodes.append(" ").append(" ").append("throw new UndeclaredThrowableException(t);").append(LINE_SEPARATOR); methodCodes.append(" ").append("}").append(LINE_SEPARATOR); /*--------------------方法體 end--------------------*/ // 方法結束 methodCodes.append("}").append(LINE_SEPARATOR).append(LINE_SEPARATOR); } return methodCodes.toString(); } }
實際上只是拼接前面給出的代理類實現而已,代碼量有點大,但并不難理解。
下一步,實現自己的類加載器,來加載生成的class字節碼:
public class MyClassLoader extends ClassLoader { private static MyClassLoader loader; private MyClassLoader() { } public static MyClassLoader getInstance() { if (loader == null) { synchronized (MyClassLoader.class) { // 得到鎖首先檢查loader是否已經存在, 避免重復創建 if (loader == null) { loader = new MyClassLoader(); } } } return loader; } /** * 加載class文件,并返回類型對象 * * @param filePath * @param className * @return * @throws ClassNotFoundException */ public Class> findClass(String filePath, String className) throws ClassNotFoundException { try { // 讀取指定class文件的字節碼 byte[] classBytes = Files.readAllBytes(Paths.get(filePath)); // 加載類并返回class類型對象 Class> clazz = defineClass(className, classBytes, 0, classBytes.length); return clazz; } catch (IOException e) { e.printStackTrace(); } throw new ClassNotFoundException(className); } }
到這一步,復雜的工作基本做完了,接下來只剩下自定義處理器類和對外接口了
自定義處理器類與Java動態代理的方式相同:
public class MyInvocationHandler implements InvocationHandler { private Object proxied; public MyInvocationHandler(Object object) { this.proxied = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("-------------------老師好[by 自定義動態代理]-------------------"); Object obj = method.invoke(proxied, args); System.out.println("-------------------老師再見[by 自定義動態代理]-------------------"); return obj; } }
對外接口:
public class MyDynamicProxy { public staticT newProxyInstance(Class clazz, InvocationHandler handler) throws Exception { // 要代理的方法: public & !final Method[] proxyMethods = Arrays.stream(clazz.getMethods()) .filter(method -> !Modifier.isFinal(method.getModifiers())) .collect(Collectors.toList()) .toArray(new Method[0]); // 生成的代理類 Class> proxyClass = MyProxyGenerator.generateAndLoadProxyClass(clazz, proxyMethods); // 代理類的構造方法 Constructor c = proxyClass.getConstructor(InvocationHandler.class); // 創建代理類對象 Object proxyObj = c.newInstance(handler); return (T) proxyObj; } }
搞定,測試一下效果:
TeacherChan proxy1 = MyDynamicProxy.newProxyInstance( TeacherChan.class, new MyInvocationHandler(new TeacherChan())); proxy1.teach(); TeacherCang proxy2 = MyDynamicProxy.newProxyInstance( TeacherCang.class, new MyInvocationHandler(new TeacherCang())); proxy2.teach();
輸出:
-------------------老師好[by 自定義動態代理]------------------- 大家好,我是陳老師,我教大家攝影! -------------------老師再見[by 自定義動態代理]------------------- -------------------老師好[by 自定義動態代理]------------------- 大家好,我是蒼老師,我教大家生物! -------------------老師再見[by 自定義動態代理]-------------------
完美!大功告成!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70841.html
摘要:內部使用了的動態代理為目標接口生成了一個動態代理類,這里會生成一個動態代理原理統一的方法攔截器,同時為接口的每個方法生成一個攔截器,并解析方法上的元數據,生成一個請求模板。的核心源碼解析到此結束了,不知道是否對您有無幫助,可留言跟我交流。 Feign是一個聲明式的Web服務客戶端。這使得Web服務客戶端的寫入更加方便 要使用Feign創建一個界面并對其進行注釋。它具有可插拔注釋支持,包...
摘要:是一個分布式服務框架,以及治理方案。手寫注意要點手寫注意要點基于上文中對于協議的理解,如果我們自己去實現,需要考慮哪些技術呢其實基于圖的整個流程應該有一個大概的理解。基于手寫實現基于手寫實現理解了協議后,我們基于來實現一個通信框架。閱讀這篇文章之前,建議先閱讀和這篇文章關聯的內容。[1]詳細剖析分布式微服務架構下網絡通信的底層實現原理(圖解)[2][年薪60W的技巧]工作了5年,你真的理解N...
摘要:如問到是否使用某框架,實際是是問該框架的使用場景,有什么特點,和同類可框架對比一系列的問題。這兩個方向的區分點在于工作方向的側重點不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個核心必考點完全解析(完) 課程預習 1.1 課程內容分為三個模塊 基礎模塊: 技術崗位與面試 計算機基礎 JVM原理 多線程 設計模式 數據結構與算法 應用模塊: 常用工具集 ...
摘要:爬蟲又一個爬蟲實現原文簡介小強當時不知道為啥選了這么個名字,又長又難記,導致編碼的過程中因為單詞的拼寫問題耽誤了好長時間。我是一個小強爬蟲線程數健壯說到健壯,這里主要體現在以下幾個方面應對封鎖這里我們使用動態代理來解決這個問題。 cockroach 爬蟲:又一個 java 爬蟲實現 原文 簡介 cockroach[小強] 當時不知道為啥選了這么個名字,又長又難記,導致編碼的過程中因為單...
摘要:自定義注解不生效原因解析及解決方法背景項目中,自己基于實現了一套緩存注解。但是最近出現一種情況緩存竟然沒有生效,大量請求被擊穿到層,導致壓力過大。至此,問題得到解決。 自定義注解不生效原因解析及解決方法 背景: 項目中,自己基于spring AOP實現了一套java緩存注解。但是最近出現一種情況:緩存竟然沒有生效,大量請求被擊穿到db層,導致db壓力過大?,F在我們看一下具體代碼情形(代...
閱讀 3793·2021-11-12 10:34
閱讀 2812·2021-09-22 15:14
閱讀 778·2019-08-30 15:53
閱讀 3196·2019-08-30 12:53
閱讀 1280·2019-08-29 18:32
閱讀 2761·2019-08-29 16:41
閱讀 1056·2019-08-26 13:40
閱讀 1795·2019-08-23 18:07