原文同步發表至我的博客【夜月歸途】html
原文連接:http://www.guitu18.com/se/java/2019-01-05/27.htmljava
本博客關於Java動態代理相關內容直達連接:數組
上篇分析的是JDK動態代理實現原理,這個下篇是一個自實現的動態代理案例,這一篇咱們自定義代理Proxy,代理業務須要實現的Handler接口,以及類加載器ClassLoader;最終咱們以本身寫的代碼去生成代理類的代碼,再用代理類的代碼去代理執行咱們的業務代碼,完成一套標準的動態代理流程;app
首先咱們分析實現代理須要什麼,下面是Proxy生成代理類的newProxyInstance()方法:ide
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
一個代理類Proxy,一個ClassLoader,一個業務類實現的接口數組,一個InvocationHandler;
把這裏的步驟拆分一下就是下面的兩步: post
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 import java.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 xianjian-mail@qq.com 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;ui
咱們自定義類加載器須要繼承ClassLoader類,重寫findClass(String name)方法,代碼以下(至關於ClassLoader):this
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 xianjian-mail@qq.com 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 xianjian-mail@qq.com 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 xianjian-mail@qq.com 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 xianjian-mail@qq.com 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繼續努力,再接再礪!