您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
Java反射获取方法参数名
 
来源:云栖社区  发布于 2016-12-7
   次浏览      
 

问题

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

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

所以说:

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

为什么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是这个样子的:

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

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

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

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

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

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

测试代码:

变量名顺序问题

用上述代码经过验证,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一样的编译结果了

 

 

   
次浏览       
相关文章

Java微服务新生代之Nacos
深入理解Java中的容器
Java容器详解
Java代码质量检查工具及使用案例
相关文档

Java性能优化
Spring框架
SSM框架简单简绍
从零开始学java编程经典
相关课程

高性能Java编程与系统性能优化
JavaEE架构、 设计模式及性能调优
Java编程基础到应用开发
JAVA虚拟机原理剖析
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

Java 中的中文编码问题
Java基础知识的三十个经典问答
玩转 Java Web 应用开发
使用Spring更好地处理Struts
用Eclipse开发iPhone Web应用
插件系统框架分析
更多...   

Struts+Spring+Hibernate
基于J2EE的Web 2.0应用开发
J2EE设计模式和性能调优
Java EE 5企业级架构设计
Java单元测试方法与技术
Java编程方法与技术

Struts+Spring+Hibernate/EJB+性能优化
华夏基金 ActiveMQ 原理与管理
某民航公司 Java基础编程到应用开发
某风电公司 Java 应用开发平台与迁移
日照港 J2EE应用开发技术框架与实践
某跨国公司 工作流管理JBPM
东方航空公司 高级J2EE及其前沿技术
更多...