前文地址

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字节码结构的分析到此就结束了

java动态代理——代理方法的假设和验证及Proxy源码分析五的更多相关文章

  1. JAVA设计模式-动态代理(Proxy)源码分析

    在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...

  2. (转)Java中的String为什么是不可变的? -- String源码分析

    背景:被问到很基础的知识点  string  自己答的很模糊 Java中的String为什么是不可变的? -- String源码分析 ps:最好去阅读原文 Java中的String为什么是不可变的 什 ...

  3. 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...

  4. java io系列04之 管道(PipedOutputStream和PipedInputStream)的简介,源码分析和示例

    本章,我们对java 管道进行学习. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/io_04.html java 管道介绍 在java中,PipedOu ...

  5. Java BAT大型公司面试必考技能视频-1.HashMap源码分析与实现

    视频通过以下四个方面介绍了HASHMAP的内容 一. 什么是HashMap Hash散列将一个任意的长度通过某种算法(Hash函数算法)转换成一个固定的值. MAP:地图 x,y 存储 总结:通过HA ...

  6. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  7. Vue.js 源码分析(五) 基础篇 方法 methods属性详解

    methods中定义了Vue实例的方法,官网是这样介绍的: 例如:: <!DOCTYPE html> <html lang="en"> <head&g ...

  8. java动态代理——字段和方法字节码的基础结构及Proxy源码分析三

    前文地址:https://www.cnblogs.com/tera/p/13280547.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...

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

    前文地址 https://www.cnblogs.com/tera/p/13336627.html 本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的 ...

随机推荐

  1. (四)pandas的拼接操作

    pandas的拼接操作 #重点 pandas的拼接分为两种: 级联:pd.concat, pd.append 合并:pd.merge, pd.join 0. 回顾numpy的级联 import num ...

  2. 数据可视化之powerBI技巧(十四)采悟:PowerBI中自制中文单位万和亿

    使用PowerBI的时候,一个很不爽之处就是数据单位的设置,只能用千.百万等英美的习惯来显示,而没有我们中文所习惯的万亿等单位,虽然要求添加"万"的呼声很高,但迟迟未见到改进动作, ...

  3. saver 的保存与恢复

    模型保存,先要创建一个Saver对象:saver=tf.train.Saver(), max_to_keep 是用来设置保存模型的个数,默认为5,即保存最近的五个模型,saver=tf.train.S ...

  4. 《串并行数据结构与算法(SML语言)实验》题解

    注意:本题解仅供参考学习,请勿直接抄袭代码,否则造成的后果和笔者无关. 第一题: 题意: 对n个数升序排序. 题解: 快排,不解释. 代码(省略了输入输出函数,下同): val n = getInt ...

  5. 【软件测试】Python自动化软件测试算是程序员吗?

    今天早上一觉醒来,突然萌生一个念头,[软件测试]软件测试算是程序员吗?左思右想,总感觉哪里不对.做了这么久的软件测试,还真没深究过这个问题.     基于,内事问百度的准则: 结果……     我刚发 ...

  6. Ethical Hacking - NETWORK PENETRATION TESTING(12)

    Post Connection Attacks Sophisticated attacks that can be used after connecting to the target AP. Ga ...

  7. Docker 入门教程(3)——Dockerfile

    Dockerfile Dockerfile是一个文本文件,用来定制镜像. 镜像是分层存储的,前一层会是下一层的基础.而镜像的定制就是定制每一层镜像在上一层做了什么改变. Dockerfile其内包含一 ...

  8. React native项目后期调整UI总结

    字体 fontSize: 14, marginLeft: 10, marginTop: 2, fontFamily: 'ABBvoiceCNSG-Regular', 布局 paddingHorizon ...

  9. Markdown画图(mermaid)学习

    简介 目前博客园支持mermaid的graph,subgraph,sequenceDiagram,gantt,classDiagram mermaid(美人鱼), 是一个类似markdown,用文本语 ...

  10. 面试题十八:在O(1)的时间内删除链表的节点

    方法一:将要删除的·节点的下一个节点的内容复制到该节点上,然后删除下一个节点注意特殊情况:链表只有一个节点时,则删除头节点,则把头节点设置为null, 如果删除的尾节点则需要顺序遍历链表,取得前序节点 ...