JDK动态代理深入理解分析并手写简易JDK动态代理(下)
原文同步发表至个人博客【夜月归途】
原文链接: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):
package com.guitu18.study.proxy.guitu; import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter; /**
* 自实现动态代理
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 15:17
*/
public class GuituDynamicProxy { /**
* 换行符
*/
private static final String LN = "\r\n";
/**
* 生成的代理类的名称,这里为了方便就不生成了,直接字符串简单定义一下
*/
private static final String SRC_NAME = "$GuituProxy0";
/**
* 生成的代理类的包名,同样为了测试方便直接定义成字符串
*/
private static final String PACKAGE_NAME = "com.guitu18.study.proxy.guitu"; /**
* 生成并返回一个代理对象
*
* @param guituClassLoader 自实现的类加载器
* @param interfaces 被代理类所实现的所有接口
* @param guituInvocationHandler 一个{@link GuituInvocationHandler}接口的实现
* 我们代理类对其代理的对象增强的代码写在对该接口的实现中
* {@link GuituProxy#invoke(Object, Method, Object[])}
* @return 返回生成的代理对象
*/
public static Object newProxyInstance(GuituClassLoader guituClassLoader,
Class<?>[] interfaces,
GuituInvocationHandler guituInvocationHandler) {
try {
// 1.动态生成源代码.java文件并写入到磁盘
File file = generateSrcToFile(interfaces); // 2.把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manage.getJavaFileObjects(file);
JavaCompiler.CompilationTask task =
compiler.getTask(null, manage, null, null, null, iterable);
task.call();
manage.close(); // 3.把编译的.class文件加载到JVM
Class proxyClass = guituClassLoader.findClass(SRC_NAME);
Constructor constructor = proxyClass.getConstructor(GuituInvocationHandler.class); // 4.返回动态生成的代理对象
return constructor.newInstance(guituInvocationHandler);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} /**
* 这里仅为理解原理和学习,代码生成简单有效即可
*
* @param interfaces 被代理类所实现的所有接口
* @return 返回生成的源代码的File对象
*/
private static File generateSrcToFile(Class<?>[] interfaces) {
try {
StringBuffer sb = new StringBuffer();
sb.append("package " + PACKAGE_NAME + ";" + LN);
sb.append("import java.lang.reflect.Method;" + LN); /**
* 实现所有接口
*/
StringBuffer interfaceStr = new StringBuffer();
for (int i = 0; i < interfaces.length; i++) {
interfaceStr.append(interfaces[i].getName());
if (interfaces.length > 1 && i < interfaces.length - 2) {
interfaceStr.append(",");
}
}
sb.append("public class " + SRC_NAME + " implements " + interfaceStr.toString() + " {" + LN);
sb.append(" GuituInvocationHandler guituInvocationHandler;" + LN);
sb.append(" public " + SRC_NAME + "(GuituInvocationHandler guituInvocationHandler) { " + LN);
sb.append(" this.guituInvocationHandler = guituInvocationHandler;" + LN);
sb.append(" }" + LN); /**
* 实现所有接口的所有方法
*/
for (Class<?> anInterface : interfaces) {
for (Method method : anInterface.getMethods()) {
// 方法形参数组
Parameter[] parameters = method.getParameters();
// 方法方法形参,类型 名称 字符串
StringBuffer paramStr = new StringBuffer();
// 方法形参类型字符串
StringBuffer paramTypeStr = new StringBuffer();
// 方法形参名称字符串
StringBuffer paramNameStr = new StringBuffer();
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
// 拼接方法形参,类型 名称
paramStr.append(parameter.getType().getName() + " " + parameter.getName());
// 拼接方法形参类型,供反射调用
paramTypeStr.append(parameter.getType().getName()).append(".class");
// 拼接方法形参名称,供反射调用
paramNameStr.append(parameter.getName());
if (parameters.length > 1 && i < parameters.length - 2) {
sb.append(", ");
paramTypeStr.append(",");
paramNameStr.append(", ");
}
}
// 生成方法
String returnTypeName = method.getReturnType().getName();
sb.append(" public " + returnTypeName + " " + method.getName() + "(" + paramStr.toString() + ") {" + LN);
sb.append(" try{" + LN);
sb.append(" Method method = " + interfaces[0].getName() +
".class.getMethod(\"" + method.getName() + "\",new Class[]{" + paramTypeStr.toString() + "});" + LN);
// 判断方法是否有返回值
if (!"void".equals(returnTypeName)) {
sb.append(" " + returnTypeName +
" invoke = (" + returnTypeName + ")this.guituInvocationHandler.invoke(this, method, new Object[]{"
+ paramNameStr.toString() + "});" + LN);
sb.append(" return invoke;" + LN);
} else {
sb.append(" this.guituInvocationHandler.invoke(this, method, null);" + LN);
}
sb.append(" }catch(Throwable e){" + LN);
sb.append(" e.printStackTrace();" + LN);
sb.append(" }" + LN);
if (!"void".equals(method.getReturnType().getName())) {
sb.append(" return null;" + LN);
}
sb.append(" }" + LN);
}
}
sb.append("}" + LN); // 将生成的字节码写入到磁盘文件
String path = GuituDynamicProxy.class.getResource("").getPath();
System.out.println(path);
File file = new File(path + SRC_NAME + ".java");
FileWriter fw = new FileWriter(file);
fw.write(sb.toString());
fw.flush();
fw.close();
return file;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
在上面的步骤中,我们先生成了代理类,然后使用JavaCompiler将其编译成class文件,接着用类加载器将class文件加载到内存,这里用到了类加载器ClassLoader;
我们自定义类加载器需要继承ClassLoader类,重写findClass(String name)方法,代码如下(相当于ClassLoader):
package com.guitu18.study.proxy.guitu; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; /**
* 自实现的类加载器
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 15:51
*/
public class GuituClassLoader extends ClassLoader { private File classPathFile; /**
* 构造方法,创建生成的文件
*/
public GuituClassLoader() {
this.classPathFile = new File(GuituClassLoader.class.getResource("").getPath());
} /**
* 获取字节码对象
*
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GuituClassLoader.class.getPackage().getName() + "." + name; if (classPathFile != null) {
File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
if (classFile.exists()) {
FileInputStream in = null;
ByteArrayOutputStream out = null; try {
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
return defineClass(className, out.toByteArray(), 0, out.size());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
return null;
}
}
接着就是接口GuituInvocationHandler如下(相当于InvocationHandler):
package com.guitu18.study.proxy.guitu; import java.lang.reflect.Method; /**
* 代理类需要实现该接口,重写invoke方法
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 15:18
*/
public interface GuituInvocationHandler { /**
* 代理类对业务增强时需要实现该方法,动态代理最终调用的是该方法的实现
*
* @param proxy 生成的代理类
* @param method 代理的方法
* @param args 代理的方法形参
* @return 返回代理执行后的结果
*/
Object invoke(Object proxy, Method method, Object[] args); }
有了这三样东西,我们就可以使用它们编写我们的动态代理了,跟上篇使用JDK动态代理时一样的使用方式,只不过使用的全都是我们自己写的代码了:
package com.guitu18.study.proxy.guitu; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; /**
* 代理类
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 16:01
*/
public class GuituProxy implements GuituInvocationHandler { private Object target; /**
* 获取代理对象
*
* @param object 被代理对象
* @return 返回代理类
*/
public Object getInstance(Object object) {
try {
this.target = object;
return GuituDynamicProxy.newProxyInstance(new GuituClassLoader(), object.getClass().getInterfaces(), this);
} catch (Exception e) {
e.printStackTrace();
}
return null;
} /**
* 代理执行前后的业务逻辑,该方法由生成的代理类调用
*
* @param proxy 代理对象
* @param method 代理执行的方法
* @param args 代理执行的方法形参
* @return 返回代理方法执行的结果,返回的Object对象由生成的代理类根据代理方法的返回值进行强转
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
System.out.println("Guitu动态代理,代理执行前...");
Object invoke = null;
invoke = method.invoke(this.target, args);
System.out.println("执行后...");
return invoke;
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
至此一套自实现的JDK动态代理就完成了,这中间很多过程直接使用的简化操作,JDK动态代理的源码比这个要复杂的多,此篇主要为了强化理解JDK动态代理思想;
具体的步骤分析和流程说明我在上面代码的注释中已经写的非常详细了,这里就不做过多说明了;这里面稍微复杂一点的就是动态的生成代理类源代码这个步骤,这里需要非常细心,毕竟使用字符串拼接代码,丝毫不能出错;其他的流程只要明白了原理其实很容易;
下面简单贴上测试代码:
package com.guitu18.study.proxy.guitu; import com.guitu18.study.proxy.Persion;
import com.guitu18.study.proxy.ZhangKuan; /**
* 自实现动态代理测试类
*
* @author zhangkuan
* @email xianjian-mail@qq.com
* @Date 2019/1/1 16:13
*/
public class GuituProxyTest { public static void main(String[] args) {
Persion instance = (Persion) new GuituProxy().getInstance(new ZhangKuan());
String love = instance.findLove("肤白貌美大长腿");
System.out.println(love);
instance.findWord();
} }
测试中的业务接口Persion和业务类ZhangKuan这里就不贴了,和上篇的代码一模一样;
执行结果如下:
Guitu动态代理,代理执行前...
肤白貌美大长腿
执行后...
叶青我爱你
Guitu动态代理,代理执行前...
我想找月薪15-25k的工作
执行后...
JDK动态代理深入分析到这里就结束了,Java学习还有很长的路要走,2019继续努力,再接再厉!
JDK动态代理深入理解分析并手写简易JDK动态代理(下)的更多相关文章
- JDK动态代理深入理解分析并手写简易JDK动态代理(上)
原文同步发表至个人博客[夜月归途] 原文链接:http://www.guitu18.com/se/java/2019-01-03/27.html 作者:夜月归途 出处:http://www.guitu ...
- 手写简易的Mybatis
手写简易的Mybatis 此篇文章用来记录今天花个五个小时写出来的简易版mybatis,主要实现了基于注解方式的增删查改,目前支持List,Object类型的查找,参数都是基于Map集合的,可以先看一 ...
- 手写简易SpringMVC
手写简易SpringMVC 手写系列框架代码基于普通Maven构建,因此在手写SpringMVC的过程中,需要手动的集成Tomcat容器 必备知识: Servlet相关理解和使用,Maven,Java ...
- 【教程】手写简易web服务器
package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...
- python-积卷神经网络全面理解-tensorflow实现手写数字识别
首先,关于神经网络,其实是一个结合很多知识点的一个算法,关于cnn(积卷神经网络)大家需要了解: 下面给出我之前总结的这两个知识点(基于吴恩达的机器学习) 代价函数: 代价函数 代价函数(Cost F ...
- UI进阶之--网易彩票手写plist文件,动态创建控制器与tableViewcell
点击右上角设置按钮 点击按钮后发生的事件:1. 控制器的跳转,进入新的控制器.view, 2. 跳转的时候对将要跳转的目标控制的子控件进行了布局.---通过手写plist文件的方式加载 为按钮注册单击 ...
- 全网最详细最好懂 PyTorch CNN案例分析 识别手写数字
先来看一下这是什么任务.就是给你手写数组的图片,然后识别这是什么数字: dataset 首先先来看PyTorch的dataset类: 我已经在从零学习pytorch 第2课 Dataset类讲解了什么 ...
- mybatis(八)手写简易版mybatis
一.画出流程图 二.设计核心类 二.V1.0 的实现 创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖. <dependency> <groupId ...
- Java多线程之Executor框架和手写简易的线程池
目录 Java多线程之一线程及其基本使用 Java多线程之二(Synchronized) Java多线程之三volatile与等待通知机制示例 线程池 什么是线程池 线程池一种线程使用模式,线程池会维 ...
随机推荐
- ES6的Module 的用法
在vue-cli中遇到的模糊参考 https://www.cnblogs.com/ppJuan/p/7151000.html 解决问题: 在 ES6 之前,社区制定了一些模块加载方案,最主要的有 Co ...
- layui 表格在排序之后没有重新渲染问题
问题描述: 在layui表格中,最后一列增加了操作按钮,并且在某些行设置了样式,但是在排序之后,按钮的点击事件失效了,样式也没有了,可能是没有执行done回调 原因: done回调只有在render和 ...
- Python基础之模块+异常
一.模块相关概念 1.定义:包含一系列数据.函数.类的文件,通常以.py结尾. 2.作用:让一些相关的数据,函数,类有逻辑的组织在一起,使逻辑结构更加清晰.有利于多人合作开发. 3.模块导入方式(三种 ...
- 问题:这个新申请的内存为什么不能free掉?(已解决)
一.问题描述 先上代码, /*** 省略 ***/ uChar *base64 = NULL; /*** 省略 ***/ base64 = (一段内存) /*** 省略 ***/ base64 = s ...
- js-day02-BOM和DOM
BOM和Document对象常见属性和方法: BOM是browser object model的缩写,简称浏览器对象模型. Document 对象每个载入浏览器的 HTML 文档都会成为 Docume ...
- 用appuploader生成发布证书和描述性文件
本帖最后由 长发飘 于 2017-4-13 12:34 编辑 之前用AppCan平台开发了一个应用,平台可以同时生成安卓版和苹果版,想着也把这应用上架到App Store试试,于是找同学借了个苹果开发 ...
- 亿级SQL Server运维的最佳实践PPT分享
这次分享是我在微软的一次分享,关于SQL Server运维最佳实践的部分,由于受众来自不同背景,因此我让分享在一个更加抽象的角度进行,PPT分享如下: 点击这里进行下载
- [Swift]LeetCode330. 按要求补齐数组 | Patching Array
Given a sorted positive integer array nums and an integer n, add/patch elements to the array such th ...
- [Swift]LeetCode541. 反转字符串 II | Reverse String II
Given a string and an integer k, you need to reverse the first k characters for every 2k characters ...
- linux入门--Linux发行版本详解
从技术上来说,李纳斯•托瓦兹开发的 Linux 只是一个内核.内核指的是一个提供设备驱动.文件系统.进程管理.网络通信等功能的系统软件,内核并不是一套完整的操作系统,它只是操作系统的核心.一些组织或厂 ...