因為一次在做項目的時候需要掃描接口的信息,其中包括參數(shù)名,遇到了點障礙就想著把這個解決方案和問題講一下。
我們要查看的方法如下
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以后,官方提供了反射的方法能獲取到接口的參數(shù)名稱。示例如下。其中g(shù)etParameters方法是1.8才開始提供的。并且需要在javac編譯時,加上-parameters參數(shù)才行。
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的字節(jié)碼,如下
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后在字節(jié)碼中記錄參數(shù)名的地方。但是1.8之前是怎么實現(xiàn)的呢?
2.spring是怎么獲取到參數(shù)名的
spring中有個ParameterNameDiscoverer接口,他有6個實現(xiàn)類。如下:

2.1 Aspect
Aspect開頭的都是對增強(qiáng)類的信息獲取。我用不到。
2.2 PrioritizedParameterNameDiscoverer
PrioritizedParameterNameDiscoverer是一個鏈表,就是記錄一系列的Discoverer。
2.3 StandardReflectionParameterNameDiscoverer
這個Discoverer就是封裝了JDK1.8的getParameters
2.4 LocalVariableTableParameterNameDiscoverer
這個類是重點,它通過asm獲取了class文件的LocalVariableTable信息。class,字節(jié)碼如下:
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"
其中有一行字節(jié)碼記錄了LocalVariableTable信息,LocalVariableTable里不僅保存了參數(shù)名,還保存了其他局部變量信息。spring通過slot來判定哪些是參數(shù)以及參數(shù)的順序。
但是LocalVariableTable不是類的必須信息,所以不是編譯后必須存在的。只有在javac時-g或-g:vars時,才會保存LocalVariableTable信息。
在idea工具中,我們可以通過如下方式,關(guān)閉編譯時,自動生成LocalVariableTable來嘗試查看字節(jié)碼。

5.DefaultParameterNameDiscoverer
這個Discoverer就是在1.8時多添加了個StandardReflectionParameterNameDiscoverer。
6.不是任何時候都能獲取到參數(shù)名
在ParameterNameDiscoverer接口上有這么段注釋:

它告訴我們,不是任何時候都能獲取到參數(shù)名的,只能嘗試去獲取。
3.spring mvc可能會獲取不到參數(shù)名嗎
當(dāng)我們關(guān)閉了class debug信息,并且將編譯級別設(shè)置為1.6時,啟動一個簡單的spring boot項目。在idea中關(guān)閉操作如下:

controller如下:
@RestController
@RequestMapping("/")
public class MainController {
@RequestMapping("/")
public String index(String info){
return info;
}
}
我們會發(fā)現(xiàn)這時候訪問該接口傳遞info參數(shù)會報如下錯誤:
java.lang.IllegalArgumentException: Name for argument type [java.lang.String] not available, and parameter name information not found in class file either.
所以,spring mvc中也是有可能獲取不到方法參數(shù)名的。如果我們需要使用spring mvc的話,最好通過Require等注解來綁定。