因为一次在做项目的时候需要扫描接口的信息,其中包括参数名,遇到了点障碍就想着把这个解决方案和问题讲一下。
我们要查看的方法如下
class Facade {
public void a(String a,String b){
int aa = 1;
for (int i = 0; i < 100; i++) {
aa++;
}
}
}
1. Java1.8以后
java1.8以后,官方提供了反射的方法能获取到接口的参数名称。示例如下。其中getParameters方法是1.8才开始提供的。并且需要在javac编译时,加上-parameters参数才行。
Method[] methods = Facade.class.getDeclaredMethods();
for (Method method : methods) {
Parameter[] parameters = method.getParameters();
for (Parameter parameter : parameters) {
System.out.println(parameter.getName());
}
}
通过javap -p -v可以查看class的字节码,如下
Classfile /Users/cey/Worksapce/weidai/gateway-extension/src/test/java/com/weidai/middleware/gateway/Facade.class
Last modified Apr 24, 2019; size 320 bytes
MD5 checksum be01198ac8c5e00915a8cbfc153deaab
Compiled from "DubboDetectorTest.java"
class Facade
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #3.#14 // java/lang/Object."<init>":()V
#2 = Class #15 // Facade
#3 = Class #16 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 a
#9 = Utf8 (Ljava/lang/String;Ljava/lang/String;)V
#10 = Utf8 MethodParameters
#11 = Utf8 b
#12 = Utf8 SourceFile
#13 = Utf8 DubboDetectorTest.java
#14 = NameAndType #4:#5 // "<init>":()V
#15 = Utf8 Facade
#16 = Utf8 java/lang/Object
{
Facade();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 54: 0
public void a(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 57: 0
MethodParameters:
Name Flags
a
b
}
SourceFile: "DubboDetectorTest.java"
其中MethodParameters就是1.8后在字节码中记录参数名的地方。但是1.8之前是怎么实现的呢?
2.spring是怎么获取到参数名的
spring中有个ParameterNameDiscoverer接口,他有6个实现类。如下:
2.1 Aspect
Aspect开头的都是对增强类的信息获取。我用不到。
2.2 PrioritizedParameterNameDiscoverer
PrioritizedParameterNameDiscoverer是一个链表,就是记录一系列的Discoverer。
2.3 StandardReflectionParameterNameDiscoverer
这个Discoverer就是封装了JDK1.8的getParameters
2.4 LocalVariableTableParameterNameDiscoverer
这个类是重点,它通过asm获取了class文件的LocalVariableTable信息。class,字节码如下:
Classfile /Users/cey/Worksapce/weidai/gateway-extension/target/test-classes/com/weidai/middleware/gateway/Facade.class
Last modified Apr 24, 2019; size 598 bytes
MD5 checksum 044e7b84601a25af47aabeb27a6bf828
Compiled from "DubboDetectorTest.java"
class com.weidai.middleware.gateway.Facade
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #3.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // com/weidai/middleware/gateway/Facade
#3 = Class #24 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/weidai/middleware/gateway/Facade;
#11 = Utf8 a
#12 = Utf8 (Ljava/lang/String;Ljava/lang/String;)V
#13 = Utf8 i
#14 = Utf8 I
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 b
#17 = Utf8 aa
#18 = Utf8 StackMapTable
#19 = Utf8 MethodParameters
#20 = Utf8 SourceFile
#21 = Utf8 DubboDetectorTest.java
#22 = NameAndType #4:#5 // "<init>":()V
#23 = Utf8 com/weidai/middleware/gateway/Facade
#24 = Utf8 java/lang/Object
{
com.weidai.middleware.gateway.Facade();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 54: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/weidai/middleware/gateway/Facade;
public void a(java.lang.String, java.lang.String);
descriptor: (Ljava/lang/String;Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=5, args_size=3
0: iconst_1
1: istore_3
2: iconst_0
3: istore 4
5: iload 4
7: bipush 100
9: if_icmpge 21
12: iinc 3, 1
15: iinc 4, 1
18: goto 5
21: return
LineNumberTable:
line 56: 0
line 57: 2
line 58: 12
line 57: 15
line 60: 21
LocalVariableTable:
Start Length Slot Name Signature
5 16 4 i I
0 22 0 this Lcom/weidai/middleware/gateway/Facade;
0 22 1 a Ljava/lang/String;
0 22 2 b Ljava/lang/String;
2 20 3 aa I
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 5
locals = [ int, int ]
frame_type = 250 /* chop */
offset_delta = 15
MethodParameters:
Name Flags
a
b
}
SourceFile: "DubboDetectorTest.java"
其中有一行字节码记录了LocalVariableTable信息,LocalVariableTable里不仅保存了参数名,还保存了其他局部变量信息。spring通过slot来判定哪些是参数以及参数的顺序。
但是LocalVariableTable不是类的必须信息,所以不是编译后必须存在的。只有在javac时-g或-g:vars时,才会保存LocalVariableTable信息。
在idea工具中,我们可以通过如下方式,关闭编译时,自动生成LocalVariableTable来尝试查看字节码。
5.DefaultParameterNameDiscoverer
这个Discoverer就是在1.8时多添加了个StandardReflectionParameterNameDiscoverer。
6.不是任何时候都能获取到参数名
在ParameterNameDiscoverer接口上有这么段注释:
它告诉我们,不是任何时候都能获取到参数名的,只能尝试去获取。
3.spring mvc可能会获取不到参数名吗
当我们关闭了class debug信息,并且将编译级别设置为1.6时,启动一个简单的spring boot项目。在idea中关闭操作如下:
controller如下:
@RestController
@RequestMapping("/")
public class MainController {
@RequestMapping("/")
public String index(String info){
return info;
}
}
我们会发现这时候访问该接口传递info参数会报如下错误:
java.lang.IllegalArgumentException: Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.
所以,spring mvc中也是有可能获取不到方法参数名的。如果我们需要使用spring mvc的话,最好通过Require等注解来绑定。