zoukankan      html  css  js  c++  java
  • JDK动态代理深入理解分析并手写简易JDK动态代理(下)

    原文同步发表至个人博客【夜月归途】

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

    本博客关于Java动态代理相关内容直达链接:

    1. JDK动态代理浅析
    2. Cglib动态代理浅析
    3. JDK动态代理深入理解分析并手写简易JDK动态代理(上)
    4. 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 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 = "
    ";
     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 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继续努力,再接再厉!

  • 相关阅读:
    linux bash shell之declare
    shell中的特殊符号
    工作常用shell集合
    sed实例收集
    vim粘贴代码时缩进混乱
    使用脚本实现killproc的功能
    zabbix 的安装
    Linux SSH 互信
    CeontOS7安装ansible
    xtrabackup 链接不上MySQL的问题
  • 原文地址:https://www.cnblogs.com/guitu18/p/10224668.html
Copyright © 2011-2022 走看看