JDK動態代理深入理解分析並手寫簡易JDK動態代理(下)
上篇分析的是JDK動態代理實現原理,這個下篇是一個自實現的動態代理案例,這一篇我們自定義代理Proxy,代理業務需要實現的Handler介面,以及類載入器ClassLoader;最終我們以自己寫的程式碼去生成代理類的程式碼,再用代理類的程式碼去代理執行我們的業務程式碼,完成一套標準的動態代理;
首先我們分析實現代理需要什麼,下面是Proxy生成代理類的newProxyInstance()方法:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
一個代理類Proxy,一個ClassLoader,一個業務類實現的介面陣列,一個InvocationHandler;
把這裡的步驟拆分一下就是下面的兩步:
1. Proxy其實就是根據傳遞給它的引數Class<?>[] interfaces去生成代理類$Proxy0;
2. 用ClassLoader loader去載入生成的這個代理類$Proxy0,然後返回$Proxy0例項的引用;
現在一步步來做,在Proxy中,我們大致可以細分為4步:
1. 動態生成代理類的原始碼.java檔案,並寫入到磁碟; 2. 把生成的.java檔案編譯成.class檔案;3. 把編譯的.class檔案載入到JVM; 4. 返回動態生成的代理物件;
那麼GuituDynamicProxy類完成後的程式碼如下(相當於Proxy):
1 package com.guitu18.study.proxy.guitu; 2 3 import javax.tools.JavaCompiler; 4 import javax.tools.StandardJavaFileManager; 5 import javax.tools.ToolProvider; 6 import java.io.File; 7 importjava.io.FileWriter; 8 import java.lang.reflect.Constructor; 9 import java.lang.reflect.Method; 10 import java.lang.reflect.Parameter; 11 12 /** 13 * 自實現動態代理 14 * 15 * @author zhangkuan 16 * @email [email protected] 17 * @Date 2019/1/1 15:17 18 */ 19 public class GuituDynamicProxy { 20 21 /** 22 * 換行符 23 */ 24 private static final String LN = "\r\n"; 25 /** 26 * 生成的代理類的名稱,這裡為了方便就不生成了,直接字串簡單定義一下 27 */ 28 private static final String SRC_NAME = "$GuituProxy0"; 29 /** 30 * 生成的代理類的包名,同樣為了測試方便直接定義成字串 31 */ 32 private static final String PACKAGE_NAME = "com.guitu18.study.proxy.guitu"; 33 34 /** 35 * 生成並返回一個代理物件 36 * 37 * @param guituClassLoader 自實現的類載入器 38 * @param interfaces 被代理類所實現的所有介面 39 * @param guituInvocationHandler 一個{@link GuituInvocationHandler}介面的實現 40 * 我們代理類對其代理的物件增強的程式碼寫在對該介面的實現中 41 * {@link GuituProxy#invoke(Object, Method, Object[])} 42 * @return 返回生成的代理物件 43 */ 44 public static Object newProxyInstance(GuituClassLoader guituClassLoader, 45 Class<?>[] interfaces, 46 GuituInvocationHandler guituInvocationHandler) { 47 try { 48 // 1.動態生成原始碼.java檔案並寫入到磁碟 49 File file = generateSrcToFile(interfaces); 50 51 // 2.把生成的.java檔案編譯成.class檔案 52 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 53 StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null); 54 Iterable iterable = manage.getJavaFileObjects(file); 55 JavaCompiler.CompilationTask task = 56 compiler.getTask(null, manage, null, null, null, iterable); 57 task.call(); 58 manage.close(); 59 60 // 3.把編譯的.class檔案載入到JVM 61 Class proxyClass = guituClassLoader.findClass(SRC_NAME); 62 Constructor constructor = proxyClass.getConstructor(GuituInvocationHandler.class); 63 64 // 4.返回動態生成的代理物件 65 return constructor.newInstance(guituInvocationHandler); 66 } catch (Exception e) { 67 e.printStackTrace(); 68 } 69 return null; 70 } 71 72 /** 73 * 這裡僅為理解原理和學習,程式碼生成簡單有效即可 74 * 75 * @param interfaces 被代理類所實現的所有介面 76 * @return 返回生成的原始碼的File物件 77 */ 78 private static File generateSrcToFile(Class<?>[] interfaces) { 79 try { 80 StringBuffer sb = new StringBuffer(); 81 sb.append("package " + PACKAGE_NAME + ";" + LN); 82 sb.append("import java.lang.reflect.Method;" + LN); 83 84 /** 85 * 實現所有介面 86 */ 87 StringBuffer interfaceStr = new StringBuffer(); 88 for (int i = 0; i < interfaces.length; i++) { 89 interfaceStr.append(interfaces[i].getName()); 90 if (interfaces.length > 1 && i < interfaces.length - 2) { 91 interfaceStr.append(","); 92 } 93 } 94 sb.append("public class " + SRC_NAME + " implements " + interfaceStr.toString() + " {" + LN); 95 sb.append(" GuituInvocationHandler guituInvocationHandler;" + LN); 96 sb.append(" public " + SRC_NAME + "(GuituInvocationHandler guituInvocationHandler) { " + LN); 97 sb.append(" this.guituInvocationHandler = guituInvocationHandler;" + LN); 98 sb.append(" }" + LN); 99 100 /** 101 * 實現所有介面的所有方法 102 */ 103 for (Class<?> anInterface : interfaces) { 104 for (Method method : anInterface.getMethods()) { 105 // 方法形引數組 106 Parameter[] parameters = method.getParameters(); 107 // 方法方法形參,型別 名稱 字串 108 StringBuffer paramStr = new StringBuffer(); 109 // 方法形參型別字串 110 StringBuffer paramTypeStr = new StringBuffer(); 111 // 方法形參名稱字串 112 StringBuffer paramNameStr = new StringBuffer(); 113 for (int i = 0; i < parameters.length; i++) { 114 Parameter parameter = parameters[i]; 115 // 拼接方法形參,型別 名稱 116 paramStr.append(parameter.getType().getName() + " " + parameter.getName()); 117 // 拼接方法形參型別,供反射呼叫 118 paramTypeStr.append(parameter.getType().getName()).append(".class"); 119 // 拼接方法形參名稱,供反射呼叫 120 paramNameStr.append(parameter.getName()); 121 if (parameters.length > 1 && i < parameters.length - 2) { 122 sb.append(", "); 123 paramTypeStr.append(","); 124 paramNameStr.append(", "); 125 } 126 } 127 // 生成方法 128 String returnTypeName = method.getReturnType().getName(); 129 sb.append(" public " + returnTypeName + " " + method.getName() + "(" + paramStr.toString() + ") {" + LN); 130 sb.append(" try{" + LN); 131 sb.append(" Method method = " + interfaces[0].getName() + 132 ".class.getMethod(\"" + method.getName() + "\",new Class[]{" + paramTypeStr.toString() + "});" + LN); 133 // 判斷方法是否有返回值 134 if (!"void".equals(returnTypeName)) { 135 sb.append(" " + returnTypeName + 136 " invoke = (" + returnTypeName + ")this.guituInvocationHandler.invoke(this, method, new Object[]{" 137 + paramNameStr.toString() + "});" + LN); 138 sb.append(" return invoke;" + LN); 139 } else { 140 sb.append(" this.guituInvocationHandler.invoke(this, method, null);" + LN); 141 } 142 sb.append(" }catch(Throwable e){" + LN); 143 sb.append(" e.printStackTrace();" + LN); 144 sb.append(" }" + LN); 145 if (!"void".equals(method.getReturnType().getName())) { 146 sb.append(" return null;" + LN); 147 } 148 sb.append(" }" + LN); 149 } 150 } 151 sb.append("}" + LN); 152 153 // 將生成的位元組碼寫入到磁碟檔案 154 String path = GuituDynamicProxy.class.getResource("").getPath(); 155 System.out.println(path); 156 File file = new File(path + SRC_NAME + ".java"); 157 FileWriter fw = new FileWriter(file); 158 fw.write(sb.toString()); 159 fw.flush(); 160 fw.close(); 161 return file; 162 } catch (Exception e) { 163 e.printStackTrace(); 164 } 165 return null; 166 } 167 }
在上面的步驟中,我們先生成了代理類,然後使用JavaCompiler將其編譯成class檔案,接著用類載入器將class檔案載入到記憶體,這裡用到了類載入器ClassLoader;
我們自定義類載入器需要繼承ClassLoader類,重寫findClass(String name)方法,程式碼如下(相當於ClassLoader):
1 package com.guitu18.study.proxy.guitu; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.IOException; 7 8 /** 9 * 自實現的類載入器 10 * 11 * @author zhangkuan 12 * @email [email protected] 13 * @Date 2019/1/1 15:51 14 */ 15 public class GuituClassLoader extends ClassLoader { 16 17 private File classPathFile; 18 19 /** 20 * 構造方法,建立生成的檔案 21 */ 22 public GuituClassLoader() { 23 this.classPathFile = new File(GuituClassLoader.class.getResource("").getPath()); 24 } 25 26 /** 27 * 獲取位元組碼物件 28 * 29 * @param name 30 * @return 31 * @throws ClassNotFoundException 32 */ 33 @Override 34 protected Class<?> findClass(String name) throws ClassNotFoundException { 35 String className = GuituClassLoader.class.getPackage().getName() + "." + name; 36 37 if (classPathFile != null) { 38 File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class"); 39 if (classFile.exists()) { 40 FileInputStream in = null; 41 ByteArrayOutputStream out = null; 42 43 try { 44 in = new FileInputStream(classFile); 45 out = new ByteArrayOutputStream(); 46 byte[] buff = new byte[1024]; 47 int len; 48 while ((len = in.read(buff)) != -1) { 49 out.write(buff, 0, len); 50 } 51 return defineClass(className, out.toByteArray(), 0, out.size()); 52 } catch (Exception e) { 53 e.printStackTrace(); 54 } finally { 55 if (null != in) { 56 try { 57 in.close(); 58 } catch (IOException e) { 59 e.printStackTrace(); 60 } 61 } 62 if (out != null) { 63 try { 64 out.close(); 65 } catch (IOException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 } 71 } 72 return null; 73 } 74 }
接著就是介面GuituInvocationHandler如下(相當於InvocationHandler):
1 package com.guitu18.study.proxy.guitu; 2 3 import java.lang.reflect.Method; 4 5 /** 6 * 代理類需要實現該介面,重寫invoke方法 7 * 8 * @author zhangkuan 9 * @email [email protected] 10 * @Date 2019/1/1 15:18 11 */ 12 public interface GuituInvocationHandler { 13 14 /** 15 * 代理類對業務增強時需要實現該方法,動態代理最終呼叫的是該方法的實現 16 * 17 * @param proxy 生成的代理類 18 * @param method 代理的方法 19 * @param args 代理的方法形參 20 * @return 返回代理執行後的結果 21 */ 22 Object invoke(Object proxy, Method method, Object[] args); 23 24 }
有了這三樣東西,我們就可以使用它們編寫我們的動態代理了,跟上篇使用JDK動態代理時一樣的使用方式,只不過使用的全都是我們自己寫的程式碼了:
1 package com.guitu18.study.proxy.guitu; 2 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 6 /** 7 * 代理類 8 * 9 * @author zhangkuan 10 * @email [email protected] 11 * @Date 2019/1/1 16:01 12 */ 13 public class GuituProxy implements GuituInvocationHandler { 14 15 private Object target; 16 17 /** 18 * 獲取代理物件 19 * 20 * @param object 被代理物件 21 * @return 返回代理類 22 */ 23 public Object getInstance(Object object) { 24 try { 25 this.target = object; 26 return GuituDynamicProxy.newProxyInstance(new GuituClassLoader(), object.getClass().getInterfaces(), this); 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } 30 return null; 31 } 32 33 /** 34 * 代理執行前後的業務邏輯,該方法由生成的代理類呼叫 35 * 36 * @param proxy 代理物件 37 * @param method 代理執行的方法 38 * @param args 代理執行的方法形參 39 * @return 返回代理方法執行的結果,返回的Object物件由生成的代理類根據代理方法的返回值進行強轉 40 */ 41 @Override 42 public Object invoke(Object proxy, Method method, Object[] args) { 43 try { 44 System.out.println("Guitu動態代理,代理執行前..."); 45 Object invoke = null; 46 invoke = method.invoke(this.target, args); 47 System.out.println("執行後..."); 48 return invoke; 49 } catch (IllegalAccessException e) { 50 e.printStackTrace(); 51 } catch (InvocationTargetException e) { 52 e.printStackTrace(); 53 } 54 return null; 55 } 56 }
至此一套自實現的JDK動態代理就完成了,這中間很多過程直接使用的簡化操作,JDK動態代理的原始碼比這個要複雜的多,此篇主要為了強化理解JDK動態代理思想;
具體的步驟分析和流程說明我在上面程式碼的註釋中已經寫的非常詳細了,這裡就不做過多說明了;這裡面稍微複雜一點的就是動態的生成代理類原始碼這個步驟,這裡需要非常細心,畢竟使用字串拼接程式碼,絲毫不能出錯;其他的流程只要明白了原理其實很容易;
下面簡單貼上測試程式碼:
1 package com.guitu18.study.proxy.guitu; 2 3 4 import com.guitu18.study.proxy.Persion; 5 import com.guitu18.study.proxy.ZhangKuan; 6 7 /** 8 * 自實現動態代理測試類 9 * 10 * @author zhangkuan 11 * @email [email protected] 12 * @Date 2019/1/1 16:13 13 */ 14 public class GuituProxyTest { 15 16 public static void main(String[] args) { 17 Persion instance = (Persion) new GuituProxy().getInstance(new ZhangKuan()); 18 String love = instance.findLove("膚白貌美大長腿"); 19 System.out.println(love); 20 instance.findWord(); 21 } 22 23 }
測試中的業務介面Persion和業務類ZhangKuan這裡就不貼了,和上篇的程式碼一模一樣;
執行結果如下:
Guitu動態代理,代理執行前... 膚白貌美大長腿 執行後... 葉青我愛你 Guitu動態代理,代理執行前... 我想找月薪15-25k的工作 執行後...
JDK動態代理深入分析到這裡就結束了,Java學習還有很長的路要走,2019繼續努力,再接再厲!