首页 > 全部内容 > 编程 > Java反射获取方法参数名
2017
08-29

Java反射获取方法参数名

问题

在编写一个jws(游戏中心的WEB框架)增强工具的时候,需要得到方法的参数名,而jws本身是可以获取参数名的(不然controller里将请求参数与方法参数绑定的功能也无法实现了).

但使用了jws提供的获取参数名方法时,却出现返回的参数名不正确的问题(只会出现在idea里面):

Java反射获取方法参数名 - 第1张  | Hello word !

所以说:

为什么可以获取方法参数?

为什么eclipse和生产环境里不会发生这种问题?

怎样可以正确获取方法的参数名?

问题排查

获取方法参数

众所周知,在java里面,直到java8才可以**正式**的通过反射获取方法参数名,而且还需要额外添加-parameters参数,官方理由是:

参数名信息会使class文件变大,让处理消耗更多的资源

容易被反编译,暴露敏感方法

所以正常来说,对java8以前的class文件进行反编译,方法参数名全部会变成var1,var2这样的东西(名字是反编译工具自己起的…).

但是某些时候,在一些WEB框架里,例如Spring MVC,JWS,却可以自动的将请求参数与对应的方法参数进行绑定.

是因为只要在编译工具javac里加上-g参数,就可以额外把本地变量名(参数也是其中一种)加到class文件中了.

javac中的debug信息

对于jvm来说,运行端代码,只需要有代码的字节码就可以了,根本不需要知道源码是什么样的.

我们之所以在ide里,可以对程序设置断点,可以对字节码反编译,是因为编译工具javac可以把一些源码相关的额外调试信息放到class文件里,具体通过-g参数控制(官方文档),可以添加的信息有:

源码文件描述信息,**默认添加**(目测没什么用,就是多了一行’Compiled from …’)

字节码与源码的行数的映射,即class文件里的LineNumberTable,**默认添加**,用于断点调试和异常栈(运行栈)中的代码行数

没有的话在ide里无法设置断点调试,且抛出的异常栈中不会显示调用的代码行数,而是显示Unknown Source

本地局部变量名表,即class文件里的LocalVariableTable,**默认不添加**,用户存放本地局部变量对应的变量名,包括参数名

所以,如果在编译时把局部变量信息放到了class文件里,运行时就可以通过字节码工具动态从class文件里拿到方法参数名了.

例子可以参考spring的org.springframework.core.LocalVariableTableParameterNameDiscoverer或以下文章.

debug信息的默认设置

在javac和ECJ(Eclipse Compiler for Java)里,调试信息的默认设置都是-g:lines,source

我们的工程用的框架之所以基本都能获取方法参数名,是因为:

jws:预编译功能通过ECJ实现,并且设置了生成LocalVariableTable

maven:compile插件默认生成所有debug信息(见设置文档),其他构建工具自行查找…

idea/eclipse:默认都生成所有debug信息

LocalVariableTable的结构

通过javap工具,可以看到方法里的LocalVariableTable是这个样子的:

Java反射获取方法参数名 - 第2张  | Hello word !

可以看到,LocalVariableTable里面的变量顺序跟程序中的顺序是不一致的,而jws里提供给外部调用的方法是直接取LocalVariableTable中前n个变量信息(n=参数个数,非static方法还会忽略掉第一个变量),自然返回的参数名就是错误的.

(但可以看到,返回的局部变量名是正确的,而不是var1之类的名称,说明class文件里是包含LocalVariableTable的)

至于为什么LocalVariableTable里的变量数据不是有序的,没有搜到确切原因(如果知道麻烦告知),但这种情况应该是正常的,因为:

可以搜到有人咨询这个问题

上述例子是本地通过官方jdk编译出来的class文件,jdk6~8结构都一致

(没理解错官方文档只说了LocalVariableTable这个Code Attribute的顺序是随意的,但没说里面的变量数据是否有序)

测试代码:

Java反射获取方法参数名 - 第3张  | Hello word !

变量名顺序问题

用上述代码经过验证,ECJ编译出来的class文件里的LocalVariableTable是有序的,而eclipse和jws都使用了这个编辑器,而idea默认是javac,所以只在idea下会出现jws获取参数名不正确的问题.

正确获取参数名

LocalVariableTable本身是一个Code Attribute,其中**Start**,**Length**和**Slot**是计算的关键.

java运行栈结构见文章中的75~91页.

Start:局部变量在方法中开始生效的偏移量,比如0就代表进入方法的时候就赋值,3代表执行到方法内的第3行命令才进行赋值

Length:局部变量生效范围的长度,比如在一个if语句里的局部变量,如果赋值偏移量是3,而if语句的结束偏移量是5,则Lenght为2

Slot:变量存放在局部变量区中的index,因为局部变量区中的空间可以复用(当一个变量失效后会被移除),所以此数字有可能重复

要正确获取对应的参数名,就需要对LocalVariableTable的数据进行排序,排序依据

参数和this的start都是0,因为在方法执行前就会生效

slot是按顺序分配空间的,实例方法的第一个临时变量一定是this,所以如果有this则slot一定是0

参数在方法上的顺序跟slot的排序结果一致,因为是按参数的顺序对参数赋值的

所以排序算法为:

先按Start排序,再按slot排序,根据实际情况看要不要去掉this(简单点start+slot然后排序就可以了)

问题解决方式

说了这么多,解决方式很简单:

修改获取方法参数名的算法,排序后再获取对应的参数名

把idea的编译器改成eclipse的,在[Preferences]->[Java Compiler]里的[Use compiler],就变成和eclipse一样的编译结果了

最后编辑:
作者:Null
这个作者貌似有点懒,什么都没有留下。