论java如何通过反射获得方法真实参数名及扩展研究

网友投稿 342 2022-08-31


论java如何通过反射获得方法真实参数名及扩展研究

前言

前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得有必要和大家分享交流一下。

示例

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

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

结果

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

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

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

调试

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

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

Spring源码

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

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

注释

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

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

实现接口类

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

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

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

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

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

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

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

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

JEP 118: Access to Parameter Names at Runtime

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

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

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

编译后的class文件

用idea打开Test1.class:

然后在View菜单中点选Show Bytecode:

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

这里我还想继续再多说一点,有关LocalVariableTable和Java class文件:class文件可以说是Java实现跨平台特性的根本!不http://管在什么平台下,只要编译出来的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-bitquantities are constructed by reading in two, four, and eight consecutive 8-bitbytes, 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 supportedby interfaces java.io.DataInput and java.io.DataOutput and classes such asjava.io.DataInputStream and java.io.DataOutputStream.

class文件结构

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

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

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

minor_version、major_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_count; u2 interfaces[interfaces_count]; 代表所实现的接口集合的大小,及接口集合本身;

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

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

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

总结

到了这里我们大致回顾一下吧,我们从尝试解决反射获得方法参数真实名称开始,了解了Java编译参数、Spring自动绑定相关处理原理、jdk8编译参数新特性、以及Java class文件的结构。通过这个过程,我们看到,就一个“自动绑定&rdqUBOyIRjauo;这个平常都感觉不到它存在的小功能背后,还有这莫多深层次的技术在里面,由此可见,Spring之所以如此强大而且易用,离不开各类底层技术的支持,这就让我想起以前看到过的一位技术博主的标语:“只有深入,方能浅出”,想想确实是这个道理。

以上就是论java如何通过反射获得方法真实参数名及扩展研究的详细内容,更多关于java反射获得真实参数名的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:# yyds干货盘点 # 使用Python实现df的奇数列与偶数列调换位置,比如A列,B列,调换成B列,A列
下一篇:【BP分类】基于花朵授粉算法优化BP神经网络实现数据分类附matlab代码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~