最近开发 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
LocalVariableTableattribute is an optional variable-length attribute in theattributestable of aCodeattribute (§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
Codeattribute is a variable-length attribute in theattributestable of amethod_infostructure (§4.6). ACodeattribute 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
nativeorabstract, and is not a class or interface initialization method, then itsmethod_infostructure must not have aCodeattribute in itsattributestable. Otherwise, itsmethod_infostructure must have exactly oneCodeattribute in itsattributestable.
即如果一个方法是 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 变量。看到这里就知道在获取方法参数名称时应该采用哪种方案了。
References
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