1、编译生成参数表
Bean2 不在 src 是避免 idea 自动编译它下面的类
public class Bean2 {
public void foo(String name, int age) {
}
}
通过命令行编译
javac -parameters Bean2.java
反编译class文件
javap -c -v Bean2.class
Last modified 2022-11-9; size 317 bytes
MD5 checksum 795ee071e0d7330ae824b462cc310feb
Compiled from "Bean2.java"
public class com.itheima.a22.Bean2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#15 // java/lang/Object."<init>":()V
#2 = Class #16 // com/itheima/a22/Bean2
#3 = Class #17 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 foo
#9 = Utf8 (Ljava/lang/String;I)V
#10 = Utf8 MethodParameters
#11 = Utf8 name
#12 = Utf8 age
#13 = Utf8 SourceFile
#14 = Utf8 Bean2.java
#15 = NameAndType #4:#5 // "<init>":()V
#16 = Utf8 com/itheima/a22/Bean2
#17 = Utf8 java/lang/Object
{
public com.itheima.a22.Bean2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public void foo(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 6: 0
MethodParameters:
Name Flags
name
age
}
SourceFile: "Bean2.java"
在结尾的 MethodParameters
属性就是,实现运行时获取方法参数的核心。这个属性是 Java 8 的 class 文件新加的,在MethodParameters保存的信息可以通过反射获取。
public class A22 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
Method foo = Bean2.class.getMethod("foo", String.class, int.class);
for (Parameter parameter : foo.getParameters()) {
System.out.println(parameter.getName());
}
}
}
结果:
name
age
2、编译生成调试信息
通过命令行编译
javac -g Bean2.java
反编译class文件
Last modified 2022-11-9; size 418 bytes
MD5 checksum 920ac6aaddfeee3c30f0f95d71e3a553
Compiled from "Bean2.java"
public class com.itheima.a22.Bean2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // com/itheima/a22/Bean2
#3 = Class #21 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/itheima/a22/Bean2;
#11 = Utf8 foo
#12 = Utf8 (Ljava/lang/String;I)V
#13 = Utf8 name
#14 = Utf8 Ljava/lang/String;
#15 = Utf8 age
#16 = Utf8 I
#17 = Utf8 SourceFile
#18 = Utf8 Bean2.java
#19 = NameAndType #4:#5 // "<init>":()V
#20 = Utf8 com/itheima/a22/Bean2
#21 = Utf8 java/lang/Object
{
public com.itheima.a22.Bean2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/itheima/a22/Bean2;
public void foo(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/itheima/a22/Bean2;
0 1 1 name Ljava/lang/String;
0 1 2 age I
}
方法参数信息保存在局部变量表中,反射是取不到,但是ASM可以获取
尝试用反射获取,结果:
arg0
arg1
由于学习成本 ASM 高,这里使用 Spring 封装好的工具类,通过本地变量表获取参数名,底层使用 ASM 获取参数名。
public class A22 {
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
LocalVariableTableParameterNameDiscoverer discover = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discover.getParameterNames(foo);
System.out.println(Arrays.toString(parameterNames));
}
}
结果:
[name, age]
但是接口不会包含局部变量表, 无法获得参数名
public interface Bean1 {
public void foo(String name, int age);
}
反编译的 class 文件
Last modified 2022-11-9; size 146 bytes
MD5 checksum 9b2656f7ac468e7de1c8edc00c936a7f
Compiled from "Bean1.java"
public interface com.itheima.a22.Bean1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Class #7 // com/itheima/a22/Bean1
#2 = Class #8 // java/lang/Object
#3 = Utf8 foo
#4 = Utf8 (Ljava/lang/String;I)V
#5 = Utf8 SourceFile
#6 = Utf8 Bean1.java
#7 = Utf8 com/itheima/a22/Bean1
#8 = Utf8 java/lang/Object
{
public abstract void foo(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: ACC_PUBLIC, ACC_ABSTRACT
}
SourceFile: "Bean1.java"
结果:
null
3、说明
spring boot 在编译时会加 -parameters,大部分 IDE 编译时都会加 -g
Spring对这俩种都支持,DefaultParameterNameDiscoverer 是上一节用过的参数名的解析器,看它的内部实现。
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
public DefaultParameterNameDiscoverer() {
if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
}
this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
}
}
1)如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名
2)如果编译时添加了 -g 可以生成调试信息, 但分为两种情况
-
普通类, 会包含局部变量表, 用 asm 可以拿到参数名
-
接口, 不会包含局部变量表, 无法获得参数名
-
这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名
-