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

前言

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

示例

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

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

结果

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

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

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

调试

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

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

Spring源码

Spring海量的源代码,从何看起呢,这里,我是这样解决的:我大体知道这个获得方法实际参数名的操作应当和MethodgetParameters()方法有关,或者说它的方法里或许会调用到这个方法,那么好了,我们可以使用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实现跨平台特性的根本!不管在什么平台下,只要编译出来的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文件可以用一个结构来表示:

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

开头的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编译参数、Spring自动绑定相关处理原理、jdk8编译参数新特性、以及Java class文件的结构。通过这个过程,我们看到,就一个“自动绑定”这个平常都感觉不到它存在的小功能背后,还有这莫多深层次的技术在里面,由此可见,Spring之所以如此强大而且易用,离不开各类底层技术的支持,这就让我想起以前看到过的一位技术博主的标语:“只有深入,方能浅出”,想想确实是这个道理。

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

(0)

相关推荐

  • java根据方法名称取得反射方法的参数类型示例

    复制代码 代码如下: /** * 根据方法名称取得反射方法的参数类型(没有考虑同名重载方法使用时注意) * @param obj         类实例   * @param methodName  方法名 * @return * @throws ClassNotFoundException */public static Class[]  getMethodParamTypes(Object classInstance,  String methodName) throws ClassNotF

  • PHP的反射动态获取类方法、属性、参数操作示例

    本文实例讲述了PHP的反射动态获取类方法.属性.参数操作.分享给大家供大家参考,具体如下: 我们可以在PHP运行时,通过PHP的反射动态的获取类的方法.属性.参数等详细信息. 用途:插件的设计,文档的自动生成,扩充PHP语言. <?php class Person { const weightUnit = 'kg'; const heightUnit = 'cm'; public $name = 'test'; public $age = 1; public function say($msg

  • java反射超详细讲解

    目录 Java反射超详解✌ 1.反射基础 1.1Class类 1.2类加载 2.反射的使用 2.1Class对象的获取 2.2Constructor类及其用法 2.4Method类及其用法 Java反射超详解✌ 1.反射基础 Java反射机制是在程序的运行过程中,对于任何一个类,都能够知道它的所有属性和方法:对于任意一个对象,都能够知道它的任意属性和方法,这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制. Java反射机制主要提供以下这几个功能: 在运行时判断任意一个对象所属

  • 详解java中反射机制(含数组参数)

    详解java中反射机制(含数组参数) java的反射是我一直非常喜欢的地方,因为有了这个,可以让程序的灵活性大大的增加,同时通用性也提高了很多.反射原理什么的,我就不想做过大介绍了,网上一搜,就一大把.(下面我是只附录介绍下) Reflection 是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等).superclass(例如O

  • Java通过反射,如何动态修改注解的某个属性值

    Java反射动态修改注解的某个属性值 昨晚看到一条问题,大意是楼主希望可以动态得建立多个Spring 的定时任务. 这个题目我并不是很熟悉,不过根据题目描述和查阅相关Spring 创建定时任务的资料,发现这也许涉及到通过Java代码动态修改注解的属性值. 今天对此尝试了一番, 发现通过反射来动态修改注解的属性值是可以做到的: 众所周知,java/lang/reflect这个包下面都是Java的反射类和工具. Annotation注解,也是位于这个包里的.注解自从Java 5.0版本引入后,就成为

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

    前言 前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题.通过解决这个问题,附带着我了解到了很多新的知识,我觉得有必要和大家分享交流一下. 示例 咱们先来看这样一个小的demo: 这是一个很简单的小demo,里面就是一个简简单单的类Test1,Test1有一个包含两个参数的方法test,在Test1的main方法中通过射来获得test方法的所有参数的名字,并将其输出到标准流.我本以为这个demo的运行结

  • Java多线程中断机制三种方法及示例

    概述 之前讲解Thread类中方法的时候,interrupt().interrupted().isInterrupted()三个方法没有讲得很清楚,只是提了一下.现在把这三个方法同一放到这里来讲,因为这三个方法都涉及到多线程的一个知识点----中断机制. Java没有提供一种安全.直接的方法来停止某个线程,而是提供了中断机制.中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理.有个例子举个蛮好,就像父母叮嘱出门在外的子女要注意身体一样,父母说了,但是子女

  • 详解获取Spring MVC中所有RequestMapping以及对应方法和参数

    在Spring MVC中想要对每一个URL进行权限控制,不想手工整理这样会有遗漏,所以就动手写程序了.代码如下: /** * @return * @author Elwin ZHANG * 创建时间:2017年3月8日 上午11:48:22 * 功能:返回系统中的所有控制器映射路径,以及对应的方法 */ @RequestMapping(value = "/maps", produces = "application/json; charset=utf-8") @Re

  • java中利用反射调用另一类的private方法的简单实例

    我们知道,Java应用程序不能访问持久化类的private方法,但Hibernate没有这个限制,它能够访问各种级别的方法,如private, default, protected, public. Hibernate是如何实现该功能的呢?答案是利用JAVA的反射机制,如下: import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectDemo {

  • 应用Java泛型和反射导出CSV文件的方法

    本文实例讲述了应用Java泛型和反射导出CSV文件的方法.分享给大家供大家参考.具体如下: 项目中有需求要把数据导出为CSV文件,因为不同的类有不同的属性,为了代码简单,应用Java的泛型和反射,写了一个函数,完成导出功能. 复制代码 代码如下: public <T> void saveFile(List<T> list, String outFile) throws IOException {         if (list == null || list.isEmpty())

  • java反射之方法反射的基本操作方法

    本文接上文"java反射之获取类的信息方法(推荐)",利用反射(invoke)来获取一个类中的方法来执行. 1.定义一个类,包含三个名称相同,参数不同的方法 class A{ public void print(){ System.out.println("Hello,World"); } public void print(int a,int b){ System.out.println(a+b); } public void print(String a,Str

  • java反射拼接方法名动态执行方法实例

    近期由于负责项目的一个模块,该模块下有很多分类,每个分类都有一个编码code,这个值是作为一个参数携带过来的.但是每个code确实对应一个方法的. code的值有很多个,自己又不想做ifelse或者switch判断于是就狂搜资料,主要让我发现利用java的反射机制可以完美的解决这个问题 测试代码如下:(可以携带多个参数哦) package com.escs.xmlutils; import java.lang.reflect.Method; public class Test { public

  • Java使用反射调用方法示例

    本文实例讲述了Java使用反射调用方法.分享给大家供大家参考,具体如下: 一 代码 import java.util.*; import java.io.*; import java.lang.reflect.*; public class ExtendedObjectPoolFactory { // 定义一个对象池,前面是对象名,后面是实际对象 private Map<String, Object> objectPool = new HashMap<>(); private Pr

  • java反射调用方法NoSuchMethodException的解决方案

    目录 java反射调用方法NoSuchMethodException NoSuchMethodException问题总结 1.编译异常,这个很容易发现并解决 2.编译正常,运行报错 java反射调用方法NoSuchMethodException 1.方法定义成 public类型. 2.getMethod传参要正确 比如调用定义的:public void   show(Object obj) 要这样调用   clazz.getMethod("show",Object.class);而不是

随机推荐