$(this) 获取不到动态元素_java动态代理——代理方法的假设和验证及Proxy源码分析五...

本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章

前文地址

java动态代理——jvm指令集基本概念和方法字节码结构的进一步探究及proxy源码分析四

此时我们先停一停,思考这样一个问题:如果由我们自己通过代码来定义一个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."":()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."":()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", "", "(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生成类的源码,我们还是需要将Proxy的字节码转换回java文件

首先我们查看ProxyGenerator类中的下面这个方法

public static byte[] generateProxyClass(final String var0, Class>[] var1, int var2)

其中的saveGeneratedFiles参数就是控制是否将生成的class文件保存到硬盘上,因此我们添加如下的jvm参数

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

e084bad56cc3dbe63ed7e3164f1b3c35.png

有了这个参数,当我们使用Proxy时,就会把class写入到文件中了

写入的目录是项目下的com/sun/proxy/$Proxy11.class为了更好地可读性,我们可以使用一个在线工具传入我们之前生成出来class文件
http://www.javadecompilers.com/

或者安装idea的插件ideajad都可以解析class文件,代码如下

package com.sun.proxy;import cn.tera.proxy.TestInterface;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy9 extends Proxy implements TestInterface {    private static Method m1;    private static Method m2;    private static Method m3;    private static Method m0;    public $Proxy9(InvocationHandler var1) throws  {        super(var1);    }    public final boolean equals(Object var1) throws  {        try {            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }    public final String toString() throws  {        try {            return (String)super.h.invoke(this, m2, (Object[])null);        } catch (RuntimeException | Error var2) {            throw var2;        } catch (Throwable var3) {            throw new UndeclaredThrowableException(var3);        }    }    public final int put(String var1) throws  {        try {            return (Integer)super.h.invoke(this, m3, new Object[]{var1});        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }    public final int hashCode() throws  {        try {            return (Integer)super.h.invoke(this, m0, (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"));            m2 = Class.forName("java.lang.Object").getMethod("toString");            m3 = Class.forName("cn.tera.proxy.TestInterface").getMethod("put", Class.forName("java.lang.String"));            m0 = Class.forName("java.lang.Object").getMethod("hashCode");        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }}

可以看到,和我们之前的猜测其实八九不离十

自然我们还可以通过javap查看class文件,从而可以将方法的指令集和之前的源码进行对比,这就留给有兴趣的读者自己研究了

至此,java动态代理的原理和相关字节码的结构学习就结束了,谢谢大家!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值