java动态代理——代理方法的假设和验证及Proxy源码分析五
2020-08-05 19:08 tera 阅读(752) 评论(0) 编辑 收藏 举报前文地址
https://www.cnblogs.com/tera/p/13419025.html
本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章
这个系列的文章的初衷是为了研究java动态代理的原理,因此最重要的一部分就是代理方法究竟是如何被定义的
此时我们先停一停,思考这样一个问题:
如果由我们自己通过代码来定义一个Proxy的动态类,我们该如何去定义?
首先回顾一下第一篇文章中提到代理类的3个特性
1.继承了Proxy类
2.实现了我们传入的接口
3.以$Proxy+随机数字的命名
假定我们现在定义一个简单的接口,并生成该接口的代理类
接口定义
public interface TestInterface { int put(String a); }
满足3个特性的代理类初步定义如下
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class $Proxy11 extends Proxy implements TestInterface { protected $Proxy11(InvocationHandler h) { super(h); } @Override public int put(String a) { return 0; } }
然而在这种情况下h的代理是无法生效的,因为put方法中并没有h的参与
现在我们回顾一下InvocationHandler的invoke方法的定义
public Object invoke(Object proxy, Method method, Object[] args)
第一个proxy是代理自身,method是被代理的方法,args是方法的参数
因此为了使得代理生效,我们可以修改方法,如下
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class $Proxy11 extends Proxy implements TestInterface { protected $Proxy11(InvocationHandler h) { super(h); } @Override public int put(String a) { try { return (int) h.invoke(this, TestInterface.class.getMethod("put", String.class), new Object[]{a}); } catch (Throwable e) { return 0; } } }
这样我们就能使得h的代理生效了
当然,这只是我们所设想的最基本的一种代理形式。有了这个思路之后,我们就可以看看源码中是如何生成方法的字节码
接着我们来看重点,proxy方法的写入
还是回到generateClassFile()方法中关注下面这行代码
this.methods.add(var16.generateMethod());
这个方法就是proxy方法实际执行的code部分了,因为代码比较多,所以我就直接将注释写到代码中
如果你前面4篇文章都没落下,那我想你一定会有兴趣看完下面所有的代码,并且会对proxy的实现和class字节码有更深刻的理解
当然,如果你看到源码就非常头疼也没有关系,可以跳过这部分源码直接看最后的验证部分
private ProxyGenerator.MethodInfo generateMethod() throws IOException { /** * 获取方法描述,如果还打开着之前javap的工具的话,就能看到类似于 * // java/lang/Object."<init>":()V * // Test.calc:(II)I */ String methodDescriptor = ProxyGenerator.getMethodDescriptor(this.parameterTypes, this.returnType); /** * 这里和之前构造器一样,先生成一个MethodInfo对象 * 这里17表示public final * Modifier.FINAL | Modifier.PUBLIC */ ProxyGenerator.MethodInfo methodInfo = ProxyGenerator.this.new MethodInfo(this.methodName, methodDescriptor, 17); /** * 新建一个存放静态池编号的数组 */ int[] parameterTypesOrders = new int[this.parameterTypes.length]; /** * 这个值是指静态池中的编号,如果还打开着之前javap的话,类似于 * Constant pool: * #1 = Methodref #8.#19 // java/lang/Object."<init>":()V * #2 = Methodref #7.#20 // Test.calc:(II)I * #3 = Double 2.0d * #5 = Methodref #21.#22 // java/lang/Math.pow:(DD)D * 前面的#1,#2,#3,#5 * 我们注意到缺少了#4,因为double需要占用8个字节,而其他的都只需要占用4个字节 */ int constantPoolNumber = 1; for(int i = 0; i < parameterTypesOrders.length; ++i) { parameterTypesOrders[i] = constantPoolNumber; /** * 如果是Long或者Double类型的参数,则+2,否则+1,因为Long和Double都是占用8个字节 */ constantPoolNumber += ProxyGenerator.getWordsPerType(this.parameterTypes[i]); } DataOutputStream dataOutputStream = new DataOutputStream(methodInfo.code); /** * aload_0,加载栈帧本地变量表的第一个参数,因为是实例方法,所以是就是指this */ ProxyGenerator.this.code_aload(0, dataOutputStream); /** * getfield,获取this的实例字段 */ dataOutputStream.writeByte(180); /** * 从Proxy类中,获取类型是InvocationHandler,字段名为h的对象 */ dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef("java/lang/reflect/Proxy", "h", "Ljava/lang/reflect/InvocationHandler;")); /** * aload_0 */ ProxyGenerator.this.code_aload(0, dataOutputStream); /** * getstatic,获取静态字段 */ dataOutputStream.writeByte(178); /** * 获取当前代理类,名字是methodFieldName,类型是Method的对象(之前在写入静态池的时候,用的也是methodFieldName) */ dataOutputStream.writeShort(ProxyGenerator.this.cp.getFieldRef(ProxyGenerator.dotToSlash(ProxyGenerator.this.className), this.methodFieldName, "Ljava/lang/reflect/Method;")); /** * 准备写入参数 */ if (this.parameterTypes.length > 0) { /** * 写入参数的数量,如果再仔细看一下code_ipush * 当length小于等于5时,写入的命令是iconst_m1~iconst_5 * 当length在-128~127闭区间时,写入的命令是bipush * 否则就写入sipush */ ProxyGenerator.this.code_ipush(this.parameterTypes.length, dataOutputStream); /** * anewarray,创建一个数组 */ dataOutputStream.writeByte(189); /** * 数组的类型是object */ dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/Object")); /** * 循环参数 */ for(int i = 0; i < this.parameterTypes.length; ++i) { /** * dup,复制栈顶的操作数 */ dataOutputStream.writeByte(89); /** * iconst、bipush、sipush */ ProxyGenerator.this.code_ipush(i, dataOutputStream); /** * 对参数类型等做一个编码 */ this.codeWrapArgument(this.parameterTypes[i], parameterTypesOrders[i], dataOutputStream); /** * aastore,将对象存入数组 */ dataOutputStream.writeByte(83); } } else { /** * 如果没参数的话 * aconst_null,push一个null */ dataOutputStream.writeByte(1); } /** * invokeinterface 调用接口方法 */ dataOutputStream.writeByte(185); /** * 找到InvocationHandler的invoke方法 */ dataOutputStream.writeShort(ProxyGenerator.this.cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;")); /** * iconst_1,将1压入操作栈 */ dataOutputStream.writeByte(4); /** * nop,不做事情 */ dataOutputStream.writeByte(0); if (this.returnType == Void.TYPE) { /** * 如果是void方法 * pop,将栈顶的操作数弹出 */ dataOutputStream.writeByte(87); /** * return */ dataOutputStream.writeByte(177); } else { /** * 对返回值进行编码 */ this.codeUnwrapReturnValue(this.returnType, dataOutputStream); } byte startPc = 0; short handlerPc; short endPc = handlerPc = (short)methodInfo.code.size(); /** * 获取方法可能抛出的异常 */ List catchList = ProxyGenerator.computeUniqueCatchList(this.exceptionTypes); if (catchList.size() > 0) { Iterator exceptionIterator = catchList.iterator(); /** * 对异常进行预处理 */ while(exceptionIterator.hasNext()) { Class var12 = (Class)exceptionIterator.next(); /** * 这里注意startPc, endPc, handlerPc参数,和pc register有关,用于抛出Exception时能确定接下去要执行的指令 */ methodInfo.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(var12.getName())))); } /** * athrow,抛出异常 */ dataOutputStream.writeByte(191); /** * 重新获取异常的处理点 */ handlerPc = (short)methodInfo.code.size(); /** * 添加异常的基类 */ dataOutputStream.exceptionTable.add(new ProxyGenerator.ExceptionTableEntry(startPc, endPc, handlerPc, ProxyGenerator.this.cp.getClass("java/lang/Throwable"))); /** * 根据constantPoolNumber的值 * astore_0 = 75 (0x4b) * astore_1 = 76 (0x4c) * astore_2 = 77 (0x4d) * astore_3 = 78 (0x4e) * astore */ ProxyGenerator.this.code_astore(constantPoolNumber, dataOutputStream); /** * new 创建一个新对象 */ dataOutputStream.writeByte(187); /** * 对象是UndeclaredThrowableException */ dataOutputStream.writeShort(ProxyGenerator.this.cp.getClass("java/lang/reflect/UndeclaredThrowableException")); /** * dup 复制栈顶操作数 */ dataOutputStream.writeByte(89); /** * 根据constantPoolNumber的值 * aload_0 = 42 (0x2a) * aload_1 = 43 (0x2b) * aload_2 = 44 (0x2c) * aload_3 = 45 (0x2d) * aload */ ProxyGenerator.this.code_aload(constantPoolNumber, dataOutputStream); /** * invokespecial,调用父类的方法 */ dataOutputStream.writeByte(183); /** * 父类的构造函数 */ dataOutputStream.writeShort(ProxyGenerator.this.cp.getMethodRef("java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V")); /** * athrow,抛出异常 */ dataOutputStream.writeByte(191); } if (var2.code.size() > 65535) { throw new IllegalArgumentException("code size limit exceeded"); } else { var2.maxStack = 10; var2.maxLocals = (short)(var4 + 1); var2.declaredExceptions = new short[this.exceptionTypes.length]; for(int var14 = 0; var14 < this.exceptionTypes.length; ++var14) { var2.declaredExceptions[var14] = ProxyGenerator.this.cp.getClass(ProxyGenerator.dotToSlash(this.exceptionTypes[var14].getName())); } return var2; } }
那么为了看看我们一开始对于方法的猜测是否正确,我们略微改造之前定义的接口和类,然后实际看看
接口和Proxy定义
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.concurrent.TimeoutException; public class Proxy11 extends Proxy implements TestInterface { protected Proxy11(InvocationHandler h) { super(h); } public void put(String a, Double b) throws TimeoutException { try { h.invoke(this, TestInterface.class.getMethod("put", String.class, Double.class), new Object[]{a, b}); } catch (Throwable e) { } } public int get(String a, Long b) throws IndexOutOfBoundsException { try { return (int) h.invoke(this, TestInterface.class.getMethod("get", String.class, Long.class), new Object[]{a, b}); } catch (Throwable e) { return 0; } } } interface TestInterface { void put(String a, Double b) throws TimeoutException; int get(String a, Long b) throws IndexOutOfBoundsException; }
我们生成class后,将字节码的指令集与我们之前的分析一一对比,虽然其中还是有些不同,不过大体上是符合之前源码的顺序
最后为了实际考察Proxy生成类的源码,我们还是需要将Proxy的字节码转换回java文件
首先我们需要添加vm启动参数
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
有了这个参数,当我们使用Proxy时,就会把class写入到文件中了
写入的目录是项目下的com/sun/proxy/$Proxy11.class
为了更好地可读性,我们需要使用一个在线工具
http://www.javadecompilers.com/
传入我们之前生成出来class文件
结果如下
package com.sun.proxy; import java.util.concurrent.TimeoutException; import java.lang.reflect.UndeclaredThrowableException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import cn.tera.aopproxy.TestInterface; import java.lang.reflect.Proxy; public final class $Proxy11 extends Proxy implements TestInterface { private static Method m1; private static Method m3; private static Method m2; private static Method m4; private static Method m0; public $Proxy11(final InvocationHandler h) { super(h); } public final boolean equals(final Object o) { try { return (boolean)super.h.invoke(this, $Proxy11.m1, new Object[] { o }); } catch (Error | RuntimeException error) { throw; } catch (Throwable undeclaredThrowable) { throw new UndeclaredThrowableException(undeclaredThrowable); } } public final int get(final String s, final Long n) throws IndexOutOfBoundsException { try { return (int)super.h.invoke(this, $Proxy11.m3, new Object[] { s, n }); } catch (Error | RuntimeException error) { throw; } catch (Throwable undeclaredThrowable) { throw new UndeclaredThrowableException(undeclaredThrowable); } } public final String toString() { try { return (String)super.h.invoke(this, $Proxy11.m2, null); } catch (Error | RuntimeException error) { throw; } catch (Throwable undeclaredThrowable) { throw new UndeclaredThrowableException(undeclaredThrowable); } } public final void put(final String s, final Double n) throws TimeoutException { try { super.h.invoke(this, $Proxy11.m4, new Object[] { s, n }); } catch (Error | RuntimeException | TimeoutException error) { throw; } catch (Throwable undeclaredThrowable) { throw new UndeclaredThrowableException(undeclaredThrowable); } } public final int hashCode() { try { return (int)super.h.invoke(this, $Proxy11.m0, null); } catch (Error | RuntimeException error) { throw; } catch (Throwable undeclaredThrowable) { throw new UndeclaredThrowableException(undeclaredThrowable); } } static { try { $Proxy11.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); $Proxy11.m3 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("get", Class.forName("java.lang.String"), Class.forName("java.lang.Long")); $Proxy11.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]); $Proxy11.m4 = Class.forName("cn.tera.aopproxy.TestInterface").getMethod("put", Class.forName("java.lang.String"), Class.forName("java.lang.Double")); $Proxy11.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]); } catch (NoSuchMethodException ex) { throw new NoSuchMethodError(ex.getMessage()); } catch (ClassNotFoundException ex2) { throw new NoClassDefFoundError(ex2.getMessage()); } } }
是不是有一种恍然大悟的感觉,此时再回头去看之前分析的方法字节码,就能更好地理解其含义了,以及和我们自己定义的类的字节码有区别的原因了。
当然我们更可以直接查看生成的class文件,再通过javap去查看字节码,然后返过去和前面的源码再作对比,这个就留给读者自己去分析了
至此,java动态代理的根本原理和相应的class字节码结构的分析到此就结束了