这个需要java编译器的支持,编译时需要添加`-parameters`参数或者启用调试模式`-g`。
以springboot为例,使用[`DefaultParameterNameDiscoverer`][6]来解析函数参数。
/**
* Default implementation of the {@link ParameterNameDiscoverer} strategy interface,
* using the Java 8 standard reflection mechanism (if available), and falling back
* to the ASM-based {@link LocalVariableTableParameterNameDiscoverer} for checking
* debug information in the class file.
*
*
Further discoverers may be added through {@link #addDiscoverer(ParameterNameDiscoverer)}.
*
* @author Juergen Hoeller
* @since 4.0
* @see StandardReflectionParameterNameDiscoverer
* @see LocalVariableTableParameterNameDiscoverer
*/
其注释指明了解析函数参数是通过反射(要求java8)或通过ASM解析class文件的中的调试信息(如果有的话)实现的,如果2者皆满足条件,则先使用反射。
目前,springboot项目的pom文件中,默认指定的java版本信息是1.6,参见[这里][7],java1.6的JVMS中已经带有了[`LocalVariableTable`属性][8]的定义,但还没有`MethodParameters`属性的定义,这个东西实在[java1.8][1]中才有的,因此注释中指明需要有java1.8的支持才能通过反射获取参数名。
springboot使用maven管理项目,默认情况下,编译(maven-)java文件时是会带有调试信息的(参见[链接: https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#debug][9]),这样就能保证,最低到java1.6版本,都可以获取到函数参数名。
debug:
Set to true to include debugging information in the compiled class files.
Type: boolean
Since: 2.0
Required: No
User Property: maven.compiler.debug
Default: true
下面是一个示例:
示例代码
package codec;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
public class Base64Test {
public static void main(String[] args) throws UnsupportedEncodingException {
String encodedStr = "ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFyZWJ1dHRvbl9kaXJlY3QwNA==";
String decodedStr = new String(Base64.getDecoder().decode(encodedStr.getBytes()));
System.out.println("Base64(" + decodedStr + ") = " + encodedStr);
}
}
使用`-parameters`编译的字节码中会带有[`MethodParameters`属性][1],使用[`StandardReflectionParameterNameDiscoverer#getParameterNames`][2]来获取。
反编译Base64Test得到:
Classfile /E:/workspace/eclipse/luna/jee/JavaTest/src/codec/Base64Test.class
Last modified 2016-7-5; size 1084 bytes
MD5 checksum 1954bc06723268e66403c281f10895a5
Compiled from "Base64Test.java"
public class codec.Base64Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #17.#30 // java/lang/Object."":()V
#2 = String #31 // ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFy
ZWJ1dHRvbl9kaXJlY3QwNA==
#3 = Class #32 // java/lang/String
#4 = Methodref #33.#34 // java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
#5 = Methodref #3.#35 // java/lang/String.getBytes:()[B
#6 = Methodref #36.#37 // java/util/Base64$Decoder.decode:([B)[B
#7 = Methodref #3.#38 // java/lang/String."":([B)V
#8 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream;
#9 = Class #41 // java/lang/StringBuilder
#10 = Methodref #9.#30 // java/lang/StringBuilder."":()V
#11 = String #42 // Base64(
#12 = Methodref #9.#43 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuil
der;
#13 = String #44 // ) =
#14 = Methodref #9.#45 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#15 = Methodref #46.#47 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Class #48 // codec/Base64Test
#17 = Class #49 // java/lang/Object
#18 = Utf8
#19 = Utf8 ()V
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 main
#23 = Utf8 ([Ljava/lang/String;)V
#24 = Utf8 Exceptions
#25 = Class #50 // java/io/UnsupportedEncodingException
#26 = Utf8 MethodParameters
#27 = Utf8 args
#28 = Utf8 SourceFile
#29 = Utf8 Base64Test.java
#30 = NameAndType #18:#19 // "":()V
#31 = Utf8 ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFyZWJ1dHRvbl9kaXJlY3
QwNA==
#32 = Utf8 java/lang/String
#33 = Class #51 // java/util/Base64
#34 = NameAndType #52:#55 // getDecoder:()Ljava/util/Base64$Decoder;
#35 = NameAndType #56:#57 // getBytes:()[B
#36 = Class #58 // java/util/Base64$Decoder
#37 = NameAndType #59:#60 // decode:([B)[B
#38 = NameAndType #18:#61 // "":([B)V
#39 = Class #62 // java/lang/System
#40 = NameAndType #63:#64 // out:Ljava/io/PrintStream;
#41 = Utf8 java/lang/StringBuilder
#42 = Utf8 Base64(
#43 = NameAndType #65:#66 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#44 = Utf8 ) =
#45 = NameAndType #67:#68 // toString:()Ljava/lang/String;
#46 = Class #69 // java/io/PrintStream
#47 = NameAndType #70:#71 // println:(Ljava/lang/String;)V
#48 = Utf8 codec/Base64Test
#49 = Utf8 java/lang/Object
#50 = Utf8 java/io/UnsupportedEncodingException
#51 = Utf8 java/util/Base64
#52 = Utf8 getDecoder
#53 = Utf8 Decoder
#54 = Utf8 InnerClasses
#55 = Utf8 ()Ljava/util/Base64$Decoder;
#56 = Utf8 getBytes
#57 = Utf8 ()[B
#58 = Utf8 java/util/Base64$Decoder
#59 = Utf8 decode
#60 = Utf8 ([B)[B
#61 = Utf8 ([B)V
#62 = Utf8 java/lang/System
#63 = Utf8 out
#64 = Utf8 Ljava/io/PrintStream;
#65 = Utf8 append
#66 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#67 = Utf8 toString
#68 = Utf8 ()Ljava/lang/String;
#69 = Utf8 java/io/PrintStream
#70 = Utf8 println
#71 = Utf8 (Ljava/lang/String;)V
{
public codec.Base64Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 6: 0
public static void main(java.lang.String[]) throws java.io.UnsupportedEncodingException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: ldc #2 // String ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFyZWJ1dHRvbl9kaXJlY3QwNA==
2: astore_1
3: new #3 // class java/lang/String
6: dup
7: invokestatic #4 // Method java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
10: aload_1
11: invokevirtual #5 // Method java/lang/String.getBytes:()[B
14: invokevirtual #6 // Method java/util/Base64$Decoder.decode:([B)[B
17: invokespecial #7 // Method java/lang/String."":([B)V
20: astore_2
21: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
24: new #9 // class java/lang/StringBuilder
27: dup
28: invokespecial #10 // Method java/lang/StringBuilder."":()V
31: ldc #11 // String Base64(
33: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload_2
37: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: ldc #13 // String ) =
42: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
45: aload_1
46: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
49: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
52: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: return
LineNumberTable:
line 9: 0
line 10: 3
line 11: 21
line 12: 55
Exceptions:
throws java.io.UnsupportedEncodingException
MethodParameters:
Name Flags
args
}
SourceFile: "Base64Test.java"
InnerClasses:
public static #53= #36 of #33; //Decoder=class java/util/Base64$Decoder of class java/util/Base64
从上面的代码中可以看到,`main`方法中有一个**MethodParameters**属性,其中列出了main方法的参数名。可被反射调用获取到。
使用`-g`编译的字节码中会带有[`LocalVariableTable`属性][3],使用[`LocalVariableTableParameterNameDiscoverer#getParameterNames`][4]来获取。
反编译class文件得到:
Classfile /E:/workspace/eclipse/luna/jee/JavaTest/src/codec/Base64Test.class
Last modified 2016-7-5; size 1228 bytes
MD5 checksum d32bafb2b4688fbd980c7688964e494c
Compiled from "Base64Test.java"
public class codec.Base64Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #17.#36 // java/lang/Object."":()V
#2 = String #37 // ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFyZWJ1dHRvbl9kaXJlY3QwNA==
#3 = Class #38 // java/lang/String
#4 = Methodref #39.#40 // java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
#5 = Methodref #3.#41 // java/lang/String.getBytes:()[B
#6 = Methodref #42.#43 // java/util/Base64$Decoder.decode:([B)[B
#7 = Methodref #3.#44 // java/lang/String."":([B)V
#8 = Fieldref #45.#46 // java/lang/System.out:Ljava/io/PrintStream;
#9 = Class #47 // java/lang/StringBuilder
#10 = Methodref #9.#36 // java/lang/StringBuilder."":()V
#11 = String #48 // Base64(
#12 = Methodref #9.#49 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#13 = String #50 // ) =
#14 = Methodref #9.#51 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#15 = Methodref #52.#53 // java/io/PrintStream.println:(Ljava/lang/String;)V
#16 = Class #54 // codec/Base64Test
#17 = Class #55 // java/lang/Object
#18 = Utf8
#19 = Utf8 ()V
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 LocalVariableTable
#23 = Utf8 this
#24 = Utf8 Lcodec/Base64Test;
#25 = Utf8 main
#26 = Utf8 ([Ljava/lang/String;)V
#27 = Utf8 args
#28 = Utf8 [Ljava/lang/String;
#29 = Utf8 encodedStr
#30 = Utf8 Ljava/lang/String;
#31 = Utf8 decodedStr
#32 = Utf8 Exceptions
#33 = Class #56 // java/io/UnsupportedEncodingException
#34 = Utf8 SourceFile
#35 = Utf8 Base64Test.java
#36 = NameAndType #18:#19 // "":()V
#37 = Utf8 ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFyZWJ1dHRvbl9kaXJlY3QwNA==
#38 = Utf8 java/lang/String
#39 = Class #57 // java/util/Base64
#40 = NameAndType #58:#61 // getDecoder:()Ljava/util/Base64$Decoder;
#41 = NameAndType #62:#63 // getBytes:()[B
#42 = Class #64 // java/util/Base64$Decoder
#43 = NameAndType #65:#66 // decode:([B)[B
#44 = NameAndType #18:#67 // "":([B)V
#45 = Class #68 // java/lang/System
#46 = NameAndType #69:#70 // out:Ljava/io/PrintStream;
#47 = Utf8 java/lang/StringBuilder
#48 = Utf8 Base64(
#49 = NameAndType #71:#72 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#50 = Utf8 ) =
#51 = NameAndType #73:#74 // toString:()Ljava/lang/String;
#52 = Class #75 // java/io/PrintStream
#53 = NameAndType #76:#77 // println:(Ljava/lang/String;)V
#54 = Utf8 codec/Base64Test
#55 = Utf8 java/lang/Object
#56 = Utf8 java/io/UnsupportedEncodingException
#57 = Utf8 java/util/Base64
#58 = Utf8 getDecoder
#59 = Utf8 Decoder
#60 = Utf8 InnerClasses
#61 = Utf8 ()Ljava/util/Base64$Decoder;
#62 = Utf8 getBytes
#63 = Utf8 ()[B
#64 = Utf8 java/util/Base64$Decoder
#65 = Utf8 decode
#66 = Utf8 ([B)[B
#67 = Utf8 ([B)V
#68 = Utf8 java/lang/System
#69 = Utf8 out
#70 = Utf8 Ljava/io/PrintStream;
#71 = Utf8 append
#72 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#73 = Utf8 toString
#74 = Utf8 ()Ljava/lang/String;
#75 = Utf8 java/io/PrintStream
#76 = Utf8 println
#77 = Utf8 (Ljava/lang/String;)V
{
public codec.Base64Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcodec/Base64Test;
public static void main(java.lang.String[]) throws java.io.UnsupportedEncodingException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=3, args_size=1
0: ldc #2 // String ZnJvbV9jb2RlPVowQkU1TSZ1bmFtZT1xcV93YW52bGR1YiZjaGFubmVsPWludml0ZV9zaGFyZWJ1dHRvbl9kaXJlY3QwNA==
2: astore_1
3: new #3 // class java/lang/String
6: dup
7: invokestatic #4 // Method java/util/Base64.getDecoder:()Ljava/util/Base64$Decoder;
10: aload_1
11: invokevirtual #5 // Method java/lang/String.getBytes:()[B
14: invokevirtual #6 // Method java/util/Base64$Decoder.decode:([B)[B
17: invokespecial #7 // Method java/lang/String."":([B)V
20: astore_2
21: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
24: new #9 // class java/lang/StringBuilder
27: dup
28: invokespecial #10 // Method java/lang/StringBuilder."":()V
31: ldc #11 // String Base64(
33: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload_2
37: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: ldc #13 // String ) =
42: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
45: aload_1
46: invokevirtual #12 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
49: invokevirtual #14 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
52: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: return
LineNumberTable:
line 9: 0
line 10: 3
line 11: 21
line 12: 55
LocalVariableTable:
Start Length Slot Name Signature
0 56 0 args [Ljava/lang/String;
3 53 1 encodedStr Ljava/lang/String;
21 35 2 decodedStr Ljava/lang/String;
Exceptions:
throws java.io.UnsupportedEncodingException
}
SourceFile: "Base64Test.java"
InnerClasses:
public static #59= #42 of #39; //Decoder=class java/util/Base64$Decoder of class java/util/Base64
从上面的代码中可以看到,`main`方法中多了一个[`LocalVariableTable`属性][3],然后spring使用[`ClassReader`][5]解析字节码文件,找出目标方法的参数名。
[1]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24
[2]: https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-core/src/main/java/org/springframework/core/StandardReflectionParameterNameDiscoverer.java#L37
[3]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.13
[4]: https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java#L68
[5]: https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-core/src/main/java/org/springframework/asm/ClassReader.java
[6]: https://github.com/spring-projects/spring-framework/blob/4.3.x/spring-core/src/main/java/org/springframework/core/DefaultParameterNameDiscoverer.java
[7]: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-parent/pom.xml#L479
[8]: https://docs.oracle.com/javase/specs/jvms/se6/html/ClassFile.doc.html#5956
[9]: https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#debug