最近开发 BPM 流程编排引擎,需要将部分 Java 方法注册为业务节点供界面上管理业务流程,此时需要获取 Java 方法的签名,其中参数名获取出来是 arg0
, arg1
, arg2
这样的,查询了下文档,.class
文件默认没有包含形式参数名称,因为含有形式参数名称时需要更大的静态及动态空间占用且部分场景形式参数名称会暴露安全敏感方法的信息,需要编译时加上 -parameters
以保留形式参数名称至运行时。
其中 JDK 中获取参数的源码位于 Parameter.java:
1 | /** |
从以上源码可以看出 arg0
, arg1
, arg2
的来源,同理在 MyBatis 框架中,引入了 @Param 注解来处理默认运行时无法获取真实参数名的问题,其中读取 @Param
注解值的源码位于 ParamNameResolver:
1 | public ParamNameResolver(Configuration config, Method method) { |
可以看出,获取方法参数名时,优先使用 @Param
注解配置的名称,如果获取不到再尝试使用 JDK 的方法获取,再获取不到就直接用序号表示。但是在 Spring MVC 中是如何获取到 Controller 层方法中的参数名并进行映射的呢?我查询了相关实现,发现 Spring MVC 是使用 ASM 获取的 class 文件中方法的 debug 信息中的参数名称,源码位于:LocalVariableTableParameterNameDiscoverer.java at v5.3.17,其中注释提到:
Implementation of ParameterNameDiscoverer that uses the LocalVariableTable information in the method attributes to discover parameter names. Returns null if the class file was compiled without debug information.
即使用方法属性中的 LocalVariableTable
信息获取参数名称,如果 class 文件编译时不含 debug 信息则返回空。查询 IDEA 的默认配置我们知道,默认编译时是带有 debug 信息的,默认配置如下:
类似地,我们发现 Maven 的编译插件配置中,debug 信息也是默认开启的,可以参考:Apache Maven Compiler Plugin – compiler:compile。那么为什么 MyBatis 不采用类似的方法解析 Mapper 接口中方法的参数名称呢?经过查询,Java Virtual Machine Specification 中给出了解释:
The
LocalVariableTable
attribute is an optional variable-length attribute in theattributes
table of aCode
attribute (§4.7.3). It may be used by debuggers to determine the value of a given local variable during the execution of a method.
即 LocalVariableTable
属性是位于 Code
属性中的,我们继续看 Code
属性的解释:
The
Code
attribute is a variable-length attribute in theattributes
table of amethod_info
structure (§4.6). ACode
attribute contains the Java Virtual Machine instructions and auxiliary information for a method, including an instance initialization method and a class or interface initialization method (§2.9.1, §2.9.2).If the method is either
native
orabstract
, and is not a class or interface initialization method, then itsmethod_info
structure must not have aCode
attribute in itsattributes
table. Otherwise, itsmethod_info
structure must have exactly oneCode
attribute in itsattributes
table.
即如果一个方法是 native
或 abstract
的,且非初始化方法,那么它的 method_info
结构中就不含 Code
属性,我们可以用 javac -g
来验证,-g
即生成 debug 信息至 class 文件中,完整的 javac
命令可参考 javac,我们先看一个简单的接口定义:
1 | public interface TestInterface { |
使用 javac -g TestInterface.java
编译该接口再使用 javap -v TestInterface
反汇编 class 文件,输出如下:
1 | Classfile /private/tmp/TestInterface.class |
与规范中描述的一致,方法属性中并不存在 Code
属性,自然也就没有 LocalVariableTable
属性,所以 MyBatis 无法通过 Spring MVC 中采用的方法获取到接口方法中的参数名称,而 Spring MVC 可以获取到是因为我们编写的 Controller 中的方法均为非抽象方法。我们再看一个普通的类:
1 | public class TestClass { |
使用 javac -g TestClass.java
编译该类再使用 javap -v TestClass
反汇编 class 文件,输出如下:
1 | Classfile /private/tmp/TestClass.class |
可见,class 文件的方法属性中是含有 Code
属性的,其中的 LocalVariableTable
属性含有每个变量的参数名称,同时我们还看到了隐式参数 this
,所以在平时的编码过程中我们可以在方法体中使用 this
变量。看到这里就知道在获取方法参数名称时应该采用哪种方案了。
Reference
Obtaining Names of Method Parameters
JEP 118: Access to Parameter Names at Runtime
Drawbacks of javac -parameters flag - Stack Overflow
Chapter 4. The class File Format - 4.7.13. The LocalVariableTable Attribute