原文同步发表至个人博客【夜月归途】
原文链接:http://www.guitu18.com/se/java/2019-01-05/27.html
出处:http://www.guitu18.com/
本博客中未标明转载的文章归作者夜月归途和博客园所有。 欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
本博客关于Java动态代理相关内容直达链接:
上篇分析的是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继续努力,再接再厉!