JDK動態代理深刻理解分析並手寫簡易JDK動態代理(下)

原文同步發表至我的博客【夜月歸途】html

原文連接:http://www.guitu18.com/se/java/2019-01-05/27.htmljava

本博客關於Java動態代理相關內容直達連接:數組

  1. JDK動態代理淺析
  2. Cglib動態代理淺析
  3. JDK動態代理深刻理解分析並手寫簡易JDK動態代理(上)
  4. JDK動態代理深刻理解分析並手寫簡易JDK動態代理(下)

上篇分析的是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繼續努力,再接再礪!

相關文章
相關標籤/搜索