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."
":()V - #2 = Class #16 // com/itheima/a22/Bean2
- #3 = Class #17 // java/lang/Object
- #4 = Utf8
- #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 // "
":()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."
":()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
通过命令行编译
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."
":()V - #2 = Class #20 // com/itheima/a22/Bean2
- #3 = Class #21 // java/lang/Object
- #4 = Utf8
- #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 // "
":()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."
":()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
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 注解来辅助获得参数名