java jdk动态代理学习记录
转载自: https://www.jianshu.com/p/3616c70cb37b
JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过在这个方法中添加某些代码,从而完成在方法前后添加一些动态的东西。JDK自带的动态代理依赖于接口,如果有些类没有接口,则不能实现动态代理。
1. 原理源码剖析
* 首先我们先来讲一下JDK动态代理的实现原理
1. 拿到被代理对象的引用,然后获取他的接口
2. JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
3. 把被代理对象的引用拿到了
4. 重新动态生成一个class字节码
5. 然后编译
* 然后先实现一个动态代理,代码很简单了,就是
实现java.lang.reflect.InvocationHandler
接口,
并使用java.lang.reflect.Proxy.newProxyInstance()
方法生成代理对象
/*** @author mark* @date 2018/3/30*/ public class JdkInvocationHandler implements InvocationHandler {private ProductService target;public Object getInstance(ProductService target){this.target = target;Class clazz = this.target.getClass();// 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");String currentDate = simpleDateFormat.format(new Date());System.out.println("日期【"+currentDate + "】添加了一款产品");return method.invoke(this.target,args);} }
被代理接口和实现
/*** 模仿产品Service* @author mark* @date 2018-03-30*/ public interface ProductService {/*** 添加产品* @param productName*/void addProduct(String productName); }/*** @author mark* @date 2018/3/30*/ public class ProductServiceImpl implements ProductService{public void addProduct(String productName) {System.out.println("正在添加"+productName);} }
测试类
public class Test {public static void main(String[] args) throws Exception {ProductService productService = new ProductServiceImpl();ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);proxy.addProduct("iphone");// 这里我们将jdk生成的代理类输出了出来,方便后面分析使用byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});FileOutputStream os = new FileOutputStream("Proxy0.class");os.write(bytes);os.close();} }
结果输出
日期【2018-03-30】添加了一款产品 正在添加iphoneProcess finished with exit code 0
上面我们实现动态动态代理的时候输出了代理类的字节码文件,现在来看一下字节码文件反编译过后的内容
import com.gwf.jdkproxy.ProductServiceImpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException;// 继承了Proxy类 public final class $Proxy0 extends Proxy implements ProductServiceImpl {private static Method m1;private static Method m8;private static Method m2;private static Method m3;private static Method m5;private static Method m4;private static Method m7;private static Method m9;private static Method m0;private static Method m6;public $Proxy0(InvocationHandler var1) throws {super(var1);}.... ..../** * 这里是代理类实现的被代理对象的接口的相同方法 */public final void addProduct(String var1) throws {try {// super.h 对应的是父类的h变量,他就是Proxy.nexInstance方法中的InvocationHandler参数// 所以这里实际上就是使用了我们自己写的InvocationHandler实现类的invoke方法super.h.invoke(this, m3, new Object[]{var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);}}public final Class getClass() throws {try {return (Class)super.h.invoke(this, m7, (Object[])null);} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);}}.... .... // 在静态构造块中,代理类通过反射获取了被代理类的详细信息,比如各种方法static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");m2 = Class.forName("java.lang.Object").getMethod("toString");m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");m0 = Class.forName("java.lang.Object").getMethod("hashCode");m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());}} }
补充一下上面代码注释中的super.h
protected InvocationHandler h;protected Proxy(InvocationHandler h) {Objects.requireNonNull(h);this.h = h;}// 这个方法是Proxy的newProxyInstance方法,主要就是生成了上面的动态字节码文件 public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{Objects.requireNonNull(h);final Class<?>[] intfs = interfaces.clone();final SecurityManager sm = System.getSecurityManager();if (sm != null) {checkProxyAccess(Reflection.getCallerClass(), loader, intfs);}/** Look up or generate the designated proxy class.*/Class<?> cl = getProxyClass0(loader, intfs);/** Invoke its constructor with the designated invocation handler.*/try {if (sm != null) {checkNewProxyPermission(Reflection.getCallerClass(), cl);}final Constructor<?> cons = cl.getConstructor(constructorParams);final InvocationHandler ih = h;if (!Modifier.isPublic(cl.getModifiers())) {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {cons.setAccessible(true);return null;}});} // 重点看这里,将我们传来的InvocationHandler参数穿给了构造函数return cons.newInstance(new Object[]{h});} catch (IllegalAccessException|InstantiationException e) {throw new InternalError(e.toString(), e);} catch (InvocationTargetException e) {Throwable t = e.getCause();if (t instanceof RuntimeException) {throw (RuntimeException) t;} else {throw new InternalError(t.toString(), t);}} catch (NoSuchMethodException e) {throw new InternalError(e.toString(), e);}}
1.拿到被代理对象的引用,然后获取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 (上面的反编译文件中实现了同样的接口)
3.把被代理对象的引用拿到了(上面被代理对象中在静态代码块中通过反射获取到的信息,以及我们实现的JdkInvocationHandler中的target)
4.重新动态生成一个class字节码
5.然后编译
2. 自己手写一个动态代理
(声明:本代码只用作实例,很多细节没有考虑进去,比如,多接口的代理类,Object类的其他默认方法的代理,为确保原汁原味,一些模板引擎和commons工具类也没有使用;觉得不足的老铁们可以随意完善,记得评论区留言完善方法哦)
我们使用jdk代理的类名和方法名定义,已经执行思路,但是所有的实现都自己来写;
首先先定义出类结构
/*** 自定义类加载器* @author gaowenfeng* @date 2018/3/30*/ public class MyClassLoader extends ClassLoader {/*** 通过类名称加载类字节码文件到JVM中* @param name 类名* @return 类的Class独享* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return super.findClass(name);} }
/*** @desc 自己实现的代理类,用来生成字节码文件,并动态加载到JVM中* @author gaowenfeng* @date 2018/3/30*/ public class MyProxy {/*** 生成代理对象* @param loader 类加载器,用于加载被代理类的类文件* @param interfaces 被代理类的接口* @param h 自定义的InvocationHandler接口,用于具体代理方法的执行* @return 返回被代理后的代理对象* @throws IllegalArgumentException*/public static Object newProxyInstance(MyClassLoader loader,Class<?>[] interfaces,MyInvocationHandler h)throws IllegalArgumentException{/*** 1.生成代理类的源代码* 2.将生成的源代码输出到磁盘,保存为.java文件* 3.编译源代码,并生成.java文件* 4.将class文件中的内容,动态加载到JVM中* 5.返回被代理后的代理对象*/return null;} }
/*** 自定义类加载器* @author gaowenfeng* @date 2018/3/30*/ public class MyClassLoader extends ClassLoader {/*** 通过类名称加载类字节码文件到JVM中* @param name 类名* @return 类的Class独享* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return super.findClass(name);} }
/*** @author gaowenfeng* @date 2018/3/30*/ public class CustomInvocationHandler implements MyInvocationHandler {private ProductService target;public Object getInstance(ProductService target){this.target = target;Class clazz = this.target.getClass();// 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3// 这里的MyClassLoader先用new的方式保证编译不报错,后面会修改return MyProxy.newProxyInstance(new MyClassLoader(),clazz.getInterfaces(),this);}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");String currentDate = simpleDateFormat.format(new Date());System.out.println("日期【"+currentDate + "】添加了一款产品");return method.invoke(this.target,args);} }
接下来我们来按照步骤一步一步的完善我们的类
- 生成代理类的源文件
/*** 生成代理类的源代码* @return*/private static String genSesource(Class<?> interfaces){StringBuilder src = new StringBuilder();src.append("package com.gwf.custom;").append(ln).append("import java.lang.reflect.Method;").append(ln).append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln).append("private MyInvocationHandler h;").append(ln).append("public $Proxy0(MyInvocationHandler h){").append(ln).append("this.h=h;").append(ln).append("}").append(ln);for(Method method:interfaces.getMethods()){src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln).append("try {").append(ln).append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln).append("this.h.invoke(this, m, new Object[]{});").append(ln).append("}catch (Throwable e){").append(ln).append("e.printStackTrace();").append(ln).append("}").append(ln).append("}").append(ln);}src.append("}");return src.toString();}
2. 将源文件保存到本地
// 1.生成代理类的源代码String src = genSesource(interfaces);// 2.将生成的源代码输出到磁盘,保存为.java文件String path = MyProxy.class.getResource("").getPath();File file = new File(path+"$Proxy0.java");FileWriter fw = new FileWriter(file);fw.write(src);fw.close();
3. 编译源代码,并生成.java文件
// 3.编译源代码,并生成.java文件// 获取java编译器JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();// 标注java文件管理器,用来获取java字节码文件StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);Iterable iterable = manager.getJavaFileObjects(file);// 创建task,通过java字节码文件将类信息加载到JVM中JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);// 开始执行task task.call();// 关闭管理器manager.close();
4. 将class文件中的内容,动态加载到JVM中
public class MyClassLoader extends ClassLoader {private String baseDir;public MyClassLoader(){this.baseDir = MyClassLoader.class.getResource("").getPath();}/*** 通过类名称加载类字节码文件到JVM中* @param name 类名* @return 类的Class独享* @throws ClassNotFoundException*/@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 获取类名String className = MyClassLoader.class.getPackage().getName()+"."+name;if(null == baseDir) {throw new ClassNotFoundException();}// 获取类文件File file = new File(baseDir,name+".class");if(!file.exists()){throw new ClassNotFoundException();}// 将类文件转换为字节数组try(FileInputStream in = new FileInputStream(file);ByteArrayOutputStream out = new ByteArrayOutputStream();){byte[] buffer = new byte[1024];int len;while ((len = in.read(buffer))!=-1){out.write(buffer,0,len);}// 调用父类方法生成class实例return defineClass(className,out.toByteArray(),0,out.size());} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}
5. 返回被代理后的代理对象
Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);return c.newInstance(h);
最后看一下总体的MyProxy类 的 newProxyInstance方法
public static Object newProxyInstance(MyClassLoader loader,Class<?> interfaces,MyInvocationHandler h)throws IllegalArgumentException{/*** 1.生成代理类的源代码* 2.将生成的源代码输出到磁盘,保存为.java文件* 3.编译源代码,并生成.java文件* 4.将class文件中的内容,动态加载到JVM中* 5.返回被代理后的代理对象*/try {// 1.生成代理类的源代码String src = genSesource(interfaces);// 2.将生成的源代码输出到磁盘,保存为.java文件String path = MyProxy.class.getResource("").getPath();File file = new File(path+"$Proxy0.java");FileWriter fw = new FileWriter(file);fw.write(src);fw.close();// 3.编译源代码,并生成.java文件JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);Iterable iterable = manager.getJavaFileObjects(file);JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);task.call();manager.close();// 4.将class文件中的内容,动态加载到JVM中Class proxyClass = loader.findClass("$Proxy0");// 5.返回被代理后的代理对象Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);return c.newInstance(h);} catch (Exception e) {e.printStackTrace();}return null;}
测试运行
public class CustomClient {public static void main(String[] args){ProductService productService = new ProductServiceImpl();ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);proxy.addProduct();} }
运行结果
日期【2018-03-30】添加了一款产品 正在添加iphoneProcess finished with exit code 0
总结:以上通过理解jdk动态代理的原理,自己手写了一个动态代理,里面涉及到的重点主要是代理类字节码的生成(这里采用通过反射强行生成源文件并编译的方法,其实应该可以直接生成字节码文件的,有兴趣的同学可以尝试)和将生成的类动态加载到JVM中(本次试验由于测试,比较简单,直接将类名硬编码到了系统里,正常应该是自动加载),虽然还不完善,但是对于理解原理应该是有狠多帮助了,欢迎同学们评论区留言评论给出更好的建议
最后附上源码地址:https://github.com/MarkGao11520/my-proxy
作者:Meet相识_bfa5
链接:https://www.jianshu.com/p/3616c70cb37b
來源:简书
转载于:https://www.cnblogs.com/huaixiaonian/p/9830876.html
java jdk动态代理学习记录相关推荐
- Java - JDK动态代理原理
Java - JDK动态代理原理 前言 一. JDK动态代理源码分析 1.1 生成目标代理类 getProxyClass0 1.1.1 KeyFactory 生成接口的虚引用 1.1.2 ProxyC ...
- JAVA 动态代理学习记录
打算用JAVA实现一个简单的RPC框架,看完RPC参考代码之后,感觉RPC的实现主要用到了两个方面的JAVA知识:网络通信和动态代理.因此,先补补动态代理的知识.---多看看代码中写的注释 参考:Ja ...
- JDK动态代理学习笔记
2019独角兽企业重金招聘Python工程师标准>>> 昨天被问了个问题,问题的大意是这样的:为什么 Proxy.newProxyInstance(ClassLoader loade ...
- java jdk动态代理 cglib动态代理demo
最近在研究java动态代理这块,以前也看了很多次java动态代理,感觉一直不是怎么明白,这两天看了看又明白了些,现给出我参考网上写的一个demo jdk动态代理实现: View Code import ...
- Java JDK 动态代理实现和代码分析
JDK 动态代理 内容 一.动态代理解析 1. 代理模式 2. 为什么要使用动态代理 3. JDK 动态代理简单结构图 4. JDK 动态代理实现步骤 5. JDK 动态代理 API 5.1 java ...
- Java JDK 动态代理
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代 ...
- 谁与争锋,JDK动态代理大战CGLib动态代理
文章目录 一.前言 二.基本概念 三.JDK 和 CGLib动态代理区别 3.1 JDK动态代理具体实现原理 3.2 CGLib动态代理 3.3 两者对比 3.4 使用注意 四.JDK 和 CGLib ...
- 【Java高级程序设计学习笔记】深入理解jdk动态代理
java的设计模式中有一项设计模式叫做代理模式,所谓代理模式,就是通过代理方来操作目标对象,而不是自己直接调用.代理又分为静态代理和动态代理,静态代理就是针对每个被代理对象写一个代理类,操作不够优雅: ...
- Spring原理学习(七)JDK动态代理与CGLIB代理底层实现
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能. 除此以外,aspectj 提供了两种另外的 AOP 底层实现: 第一种是通过 ajc 编译器在编译 class 类文件时,就把通知 ...
最新文章
- __new__ __init__区别
- 稀疏性如何为AI推理增加难度
- gridcontrol值为0时设置为空_XASSET 4.0入门指南
- python统计文件行数检测字符串_python统计文件中的字符串数目示例
- 组建核心团队时的困惑
- 【bzoj 2460 [BeiJing2011]元素】
- 思科超融合:主推HyperFlex,押注HCI
- 剑指offer(26-33题)详解
- 关于数据契约(DataContract)待续
- windows制作docker镜像_.NET Core程序跑在任何有docker的地方
- 某烟草局绩效考核系统分析设计清单
- LTE下行资源分配type0、type1、type2
- 最新互联网架构师视频教程+源码20G
- 1.Influxdb使用1
- MOSS系列之三列表和文档库[转帖]
- cygwin linux 教程,Cygwin工具使用入门教程
- oj苹果和虫子c语言,用C++实现苹果和虫子问题
- mysql8.0.13解压版安装密码设置_mysql8.0.13解压版安装配置方法图文教程
- Matlab:数据分析与多项式计算
- 解决二义性问题解决 java_C++中常见的两种二义性问题及其解决方式
热门文章
- python3扫雷代码_GitHub - pantaduce/minesweeper: Python代码编写的扫雷游戏
- python中哪里用到缩进_Python编程常见十大错误
- Valve CEO:脑机接口技术可加强游戏体验远超“肉体外设“
- 华人博士拿下ACM SIGSOFT杰出博士论文奖,师从北大谢涛教授
- 谷歌实现2种新的强化学习算法,“比肩”DQN,泛化性能更佳!|ICLR 2021
- 5位华人学生开发出了机器导盲犬!四足机器人技能又+1
- 报名 | 四场直播讲透AI芯片的应用与挑战、剖析技术难点,芯片的所有知识点都在这了...
- 硅谷“封城”前夜的L4级别无人车试乘实况,及其背后创新技术的深度剖析
- 一场实验室意外爆炸事故,解决了58年量子难题,让科学家意外发现“核电共振”...
- NLP中文面试学习资料:面向算法面试,理论代码俱全,登上GitHub趋势榜