首页 > 编程学习 > SpringMVC原理学习(三)获取参数名

SpringMVC原理学习(三)获取参数名

发布时间:2022/11/10 1:15:55

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 注解来辅助获得参数名

Copyright © 2010-2022 dgrt.cn 版权所有 |关于我们| 联系方式