温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

java怎么通过反射获得方法真实参数名

发布时间:2022-01-26 14:14:21 来源:亿速云 阅读:359 作者:zzz 栏目:开发技术

今天小编给大家分享一下java怎么通过反射获得方法真实参数名的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

示例

咱们先来看这样一个小的demo:

java怎么通过反射获得方法真实参数名

这是一个很简单的小demo,里面就是一个简简单单的类Test1Test1有一个包含两个参数的方法test,在Test1main方法中通过射来获得test方法的所有参数的名字,并将其输出到标准流。我本以为这个demo的运行结果会得到方法的参数名。

结果

java怎么通过反射获得方法真实参数名

惊不惊喜,意不意外?和说好的不一样啊!

咱们先停一下,先把为什么反射没有拿到正确的值放到一边,先说说我为什么要研究“通过反射原理获得方法参数的实际名称”这件事呢:是因为我想仿照并实现Spring MVC中的“自动绑定”功能。大家知道Spring MVC里有一个“自动绑定”的功能,能够自动绑定请求参数的值到@RequestMapping方法的参数上的,而不用任何额外的操作。

java怎么通过反射获得方法真实参数名

这个功能我觉得很方便,所以我想尝试自己仿造这个功能,然后用在公司的项目开发中。我猜测Spring是通过反射获得方法的参数名后根据参数名到requestgetParam(String name)来获得实际的值然后绑定的。因此我就尝试着按照这个思路做,结果就遇到了上边提到的反射获得不了参数实际名称的问题。我将这个问题请教了老大,老大了解到我的意图后,经过验证,得出结论:Spring MVC能不能正常使用自动绑定是与java编译器编译时加不加-g参数有关的,而这个-g参数是代表着java编译器在编译时是否会输出调试信息。

调试

java怎么通过反射获得方法真实参数名

其实也就是说:Spring是通过读取java编译器生成的调试信息从而获得的方法中参数的真实名称的。说到这里,这个问题基本也解决了,但是我还是想再多说一点我后续的学习结果。后续我研究了一下Spring对于方法参数这块的处理逻辑,也就是对于“自动绑定”功能的底层的实现。

那么,Spring 到底是用了什么“黑科技”来做到获得方法实际参数名的呢,咱们不妨就看Spring的源码吧,看看Spring到底是如何实现的。

Spring源码

Spring海量的源代码,从何看起呢,这里,我是这样解决的:我大体知道这个获得方法实际参数名的操作应当和MethodgetParameters()方法有关,或者说它的方法里或许会调用到这个方法,那么好了,我们可以使用idea提供的“查看调用栈”的功能,来顺藤摸瓜,看看在Spring中有没有调用到这个方法,如果有,那么解决方案应当就在调用方法的附近。

java怎么通过反射获得方法真实参数名

我们可以看到,果不其然,在调用栈里就有org.spring包中的方法,其中有两个方法都是StandardReflectionParameterNameDiscoverer类的方法,其实我们已经找到了,看这个类的名字就能知道,它是处理ParameterName的Discoverer的(在这里我想再说点题外话,我个人非常赞同Spring这种全命名的编码风格,看到命名就能看明白这个类是在干什么,所以说代码应当是能“自描述”的)

java怎么通过反射获得方法真实参数名

java怎么通过反射获得方法真实参数名

注释

好,我们再回到代码中来,继续看这个类:发现它有一段简要的注释:

java怎么通过反射获得方法真实参数名

大意就是这个类是针对使用了JDK8基于-parameters编译参数的ParameterNameDiscoverer的实现,这里这个-parameters参数是怎么回事咱们先放一边。

实现接口类

继续向上看StandardReflectionParameterNameDiscoverer所实现的这个接口ParameterNameDiscoverer,打开ParameterNameDiscoverer这个接口,我们用idea的查看子类的功能,能够看到它一共有包括StandardReflectionParameterNameDiscoverer在内的8个子类

java怎么通过反射获得方法真实参数名

其中有一个名字里带“Default”的子类DefaultParameterNameDiscoverer,按照一般套路来说,带Default的都是默认的实现,那么好了我们优先看它吧。

java怎么通过反射获得方法真实参数名

打开DefaultParameterNameDiscoverer,我们发现,他做的大体就是通过判断standardReflectionAvailable这个值来走向不同分支流程:一个是走向刚才提到的利用JDK8编译参数的StandardReflectionParameterNameDiscoverer另一个是走向了LocalVariableTableParameterNameDiscoverer

java怎么通过反射获得方法真实参数名

好,现在又出现了熟悉的StandardReflectionParameterNameDiscoverer了,那么我们返回去看吧,一会再看另一个分支的LocalVariableTableParameterNameDiscoverer

我们回到StandardReflectionParameterNameDiscoverer中,再来看刚才那个-parameters编译参数,这是个什么黑科技?既然他是个编译参数,那么咱们不妨试着用它编译一下咱们的代码试一下吧。

java怎么通过反射获得方法真实参数名

我们将idea设置上-parameters编译参数从新运行刚才的demo,发现这回的输出结果是:

java怎么通过反射获得方法真实参数名

已经能够拿到参数的真实名称了。那么,这个-parameters到底是什么呢:我们可以来看一下oracle官方提供的javac文档:

java怎么通过反射获得方法真实参数名

java怎么通过反射获得方法真实参数名

通过文档可以看出加上这个参数后,编译器会生成元数据,从而使方法参数的反射能够拿到参数的信息。
这个功能是jdk8的新特性,我们就不仔细展开了,详情可以查看这两篇文档:
JDK 8 Features

JEP 118: Access to Parameter Names at Runtime

-parameters这个黑科技咱们已经了解了,利用这个编译参数是可以获得方法参数的真实名称的,但是这个参数是jdk8之后才有的,那么之前的版本如何获得呢?我们继续看Spring源代码吧。现在我们来看另一个分支:LocalVariableTableParameterNameDiscoverer,打开这个类:

java怎么通过反射获得方法真实参数名

其实看注释就明白了,这个LocalVariableTableParameterNameDiscoverer是通过ASM library分析LocalVariableTable来实现获得参数实际名称的,ASM是一个第三方的字节码操纵库,用这个库可以读取写入class文件,这个库有很广泛的应用,具体的我不展开介绍了。

java怎么通过反射获得方法真实参数名

我们重点说一下这个LocalVariableTable吧,这个LocalVariableTable是什么呢?我们不用文字来说明了,直接来看代码吧:
我们这次不看源文件了,来直接看编译后的class文件。

编译后的class文件

idea打开Test1.class

java怎么通过反射获得方法真实参数名

然后在View菜单中点选Show Bytecode

java怎么通过反射获得方法真实参数名

在弹出窗口中,我们可以看到,idea以大纲的方式把class文件的信息列了出来,而在其中就有LocalVariableTable存在,而且在“LocalVariableTable”附近我们可以看到我们定义方法的参数的真实名称。现在我们也就明白了,对于8以下的jdk编译环境,Spring是使用ASM来读取class文件中LocalVariableTable信息从而获得参数真实名称的。
到此为止,我们已经基本了解了Spring中自动绑定背后的黑科技了。

这里我还想继续再多说一点,有关LocalVariableTable和Java class文件:class文件可以说是Java实现跨平台特性的根本!不管在什么平台下,只要编译出来的class文件符合规范,虚拟机就能够正常的执行。了解一下class文件的相关知识其实对于理解各类class文件操纵库以及基于class操纵的AOP等等编程模式的原理是很有帮助的,所以我们可以了解一下class文件是什么样的结构的。想要了解class文件的结构,最权威的莫过于官方的《Java虚拟机规范了》,在Java虚拟机规范中,第四章是有关class文件结构的内容,我们可以大致过一遍。
通过阅读,我们可以大致了解到class的结构:

A class file consists of a stream of 8-bit bytes. All 16-bit, 32-bit, and 64-bit
quantities are constructed by reading in two, four, and eight consecutive 8-bit
bytes, respectively. Multibyte data items are always stored in big-endian order,
where the high bytes come first. In the Java SE platform, this format is supported
by interfaces java.io.DataInput and java.io.DataOutput and classes such as
java.io.DataInputStream and java.io.DataOutputStream.

class文件结构

class文件可以用一个结构来表示:

java怎么通过反射获得方法真实参数名

这个结构中每一项大致的含义我们来简单说明一下吧(详情请查看虚拟机规范):

开头的magic u4叫做“魔数”,Java虚拟器通过读取这个数来判断当前文件是不是有效的u4代表它是无符号4byte,这个数始终应该是0xCAFEBABE

minor_versionmajor_version分别是class文件的次版本主版本

u2 constant_pool_count 、cp_info constant_pool[constant_pool_count-1]代表常量池中项目数和代表了常量池本身;

u2 access_flags : 代表class访问标记,例如:public protected;

u2 this_class : 代表放置类名在常量池中的索引;

u2 super_class : 代表父类名称在常量池中的索引;

u2 interfaces_countu2 interfaces[interfaces_count]; 代表所实现的接口集合的大小,及接口集合本身;

u2 fields_countfield_info fields[fields_count]; 代表属性集合大小以及属性集合本身;

u2 methods_countmethod_info methods[methods_count]; 代表方法集合大小以及方法集合本身;

u2 attributes_countattribute_info attributes[attributes_count]; java class文件内部属性信息集合大小和内部属性信息集合本身。这里提一下,我们前面的提到的LocalVariableTable的信息就存储在这里。

以上就是“java怎么通过反射获得方法真实参数名”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI