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


    1、编译生成参数表

    Bean2 不在 src 是避免 idea 自动编译它下面的类

     

    1. public class Bean2 {
    2. public void foo(String name, int age) {
    3. }
    4. }

    通过命令行编译

    javac -parameters Bean2.java
    

    反编译class文件

    javap -c -v Bean2.class
    1. Last modified 2022-11-9; size 317 bytes
    2. MD5 checksum 795ee071e0d7330ae824b462cc310feb
    3. Compiled from "Bean2.java"
    4. public class com.itheima.a22.Bean2
    5. minor version: 0
    6. major version: 52
    7. flags: ACC_PUBLIC, ACC_SUPER
    8. Constant pool:
    9. #1 = Methodref #3.#15 // java/lang/Object."":()V
    10. #2 = Class #16 // com/itheima/a22/Bean2
    11. #3 = Class #17 // java/lang/Object
    12. #4 = Utf8
    13. #5 = Utf8 ()V
    14. #6 = Utf8 Code
    15. #7 = Utf8 LineNumberTable
    16. #8 = Utf8 foo
    17. #9 = Utf8 (Ljava/lang/String;I)V
    18. #10 = Utf8 MethodParameters
    19. #11 = Utf8 name
    20. #12 = Utf8 age
    21. #13 = Utf8 SourceFile
    22. #14 = Utf8 Bean2.java
    23. #15 = NameAndType #4:#5 // "":()V
    24. #16 = Utf8 com/itheima/a22/Bean2
    25. #17 = Utf8 java/lang/Object
    26. {
    27. public com.itheima.a22.Bean2();
    28. descriptor: ()V
    29. flags: ACC_PUBLIC
    30. Code:
    31. stack=1, locals=1, args_size=1
    32. 0: aload_0
    33. 1: invokespecial #1 // Method java/lang/Object."":()V
    34. 4: return
    35. LineNumberTable:
    36. line 3: 0
    37. public void foo(java.lang.String, int);
    38. descriptor: (Ljava/lang/String;I)V
    39. flags: ACC_PUBLIC
    40. Code:
    41. stack=0, locals=3, args_size=3
    42. 0: return
    43. LineNumberTable:
    44. line 6: 0
    45. MethodParameters:
    46. Name Flags
    47. name
    48. age
    49. }
    50. SourceFile: "Bean2.java"

    在结尾的 MethodParameters 属性就是,实现运行时获取方法参数的核心。这个属性是 Java 8 的 class 文件新加的,在MethodParameters保存的信息可以通过反射获取。

    1. public class A22 {
    2. public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
    3. Method foo = Bean2.class.getMethod("foo", String.class, int.class);
    4. for (Parameter parameter : foo.getParameters()) {
    5. System.out.println(parameter.getName());
    6. }
    7. }
    8. }

    结果:

    1. name
    2. age

    2、编译生成调试信息

    通过命令行编译

    javac -g Bean2.java
    

    反编译class文件

    1. Last modified 2022-11-9; size 418 bytes
    2. MD5 checksum 920ac6aaddfeee3c30f0f95d71e3a553
    3. Compiled from "Bean2.java"
    4. public class com.itheima.a22.Bean2
    5. minor version: 0
    6. major version: 52
    7. flags: ACC_PUBLIC, ACC_SUPER
    8. Constant pool:
    9. #1 = Methodref #3.#19 // java/lang/Object."":()V
    10. #2 = Class #20 // com/itheima/a22/Bean2
    11. #3 = Class #21 // java/lang/Object
    12. #4 = Utf8
    13. #5 = Utf8 ()V
    14. #6 = Utf8 Code
    15. #7 = Utf8 LineNumberTable
    16. #8 = Utf8 LocalVariableTable
    17. #9 = Utf8 this
    18. #10 = Utf8 Lcom/itheima/a22/Bean2;
    19. #11 = Utf8 foo
    20. #12 = Utf8 (Ljava/lang/String;I)V
    21. #13 = Utf8 name
    22. #14 = Utf8 Ljava/lang/String;
    23. #15 = Utf8 age
    24. #16 = Utf8 I
    25. #17 = Utf8 SourceFile
    26. #18 = Utf8 Bean2.java
    27. #19 = NameAndType #4:#5 // "":()V
    28. #20 = Utf8 com/itheima/a22/Bean2
    29. #21 = Utf8 java/lang/Object
    30. {
    31. public com.itheima.a22.Bean2();
    32. descriptor: ()V
    33. flags: ACC_PUBLIC
    34. Code:
    35. stack=1, locals=1, args_size=1
    36. 0: aload_0
    37. 1: invokespecial #1 // Method java/lang/Object."":()V
    38. 4: return
    39. LineNumberTable:
    40. line 3: 0
    41. LocalVariableTable:
    42. Start Length Slot Name Signature
    43. 0 5 0 this Lcom/itheima/a22/Bean2;
    44. public void foo(java.lang.String, int);
    45. descriptor: (Ljava/lang/String;I)V
    46. flags: ACC_PUBLIC
    47. Code:
    48. stack=0, locals=3, args_size=3
    49. 0: return
    50. LineNumberTable:
    51. line 6: 0
    52. LocalVariableTable:
    53. Start Length Slot Name Signature
    54. 0 1 0 this Lcom/itheima/a22/Bean2;
    55. 0 1 1 name Ljava/lang/String;
    56. 0 1 2 age I
    57. }

    方法参数信息保存在局部变量表中,反射是取不到,但是ASM可以获取

    尝试用反射获取,结果:

    1. arg0
    2. arg1

    由于学习成本 ASM 高,这里使用 Spring 封装好的工具类,通过本地变量表获取参数名,底层使用 ASM 获取参数名。

    1. public class A22 {
    2. public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException {
    3. LocalVariableTableParameterNameDiscoverer discover = new LocalVariableTableParameterNameDiscoverer();
    4. String[] parameterNames = discover.getParameterNames(foo);
    5. System.out.println(Arrays.toString(parameterNames));
    6. }
    7. }

     结果:

    [name, age]

    但是接口不会包含局部变量表, 无法获得参数名

    1. public interface Bean1 {
    2. public void foo(String name, int age);
    3. }

     反编译的 class 文件

    1. Last modified 2022-11-9; size 146 bytes
    2. MD5 checksum 9b2656f7ac468e7de1c8edc00c936a7f
    3. Compiled from "Bean1.java"
    4. public interface com.itheima.a22.Bean1
    5. minor version: 0
    6. major version: 52
    7. flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
    8. Constant pool:
    9. #1 = Class #7 // com/itheima/a22/Bean1
    10. #2 = Class #8 // java/lang/Object
    11. #3 = Utf8 foo
    12. #4 = Utf8 (Ljava/lang/String;I)V
    13. #5 = Utf8 SourceFile
    14. #6 = Utf8 Bean1.java
    15. #7 = Utf8 com/itheima/a22/Bean1
    16. #8 = Utf8 java/lang/Object
    17. {
    18. public abstract void foo(java.lang.String, int);
    19. descriptor: (Ljava/lang/String;I)V
    20. flags: ACC_PUBLIC, ACC_ABSTRACT
    21. }
    22. SourceFile: "Bean1.java"

     结果:

    null

    3、说明

    spring boot 在编译时会加 -parameters,大部分 IDE 编译时都会加 -g

    Spring对这俩种都支持,DefaultParameterNameDiscoverer 是上一节用过的参数名的解析器,看它的内部实现。

    1. public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
    2. public DefaultParameterNameDiscoverer() {
    3. if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
    4. this.addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
    5. }
    6. this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
    7. this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
    8. }
    9. }

    1)如果编译时添加了 -parameters 可以生成参数表, 反射时就可以拿到参数名

    2)如果编译时添加了 -g 可以生成调试信息, 但分为两种情况

    • 普通类, 会包含局部变量表, 用 asm 可以拿到参数名

    • 接口, 不会包含局部变量表, 无法获得参数名

      • 这也是 MyBatis 在实现 Mapper 接口时为何要提供 @Param 注解来辅助获得参数名

  • 相关阅读:
    物联网感知-分布式光纤振动传感主机实现基本原理
    Wpf 使用 Prism 实战开发Day01
    柔性数组
    全网最全的混合精度训练原理
    【Java】<泛型>,在编译阶段约束操作的数据结构,并进行检查。
    微服务间的测试策略
    在 Python 3.9 中删除前缀和后缀的新字符串方法
    Java关键字volatile
    前端周刊第二十二期
    element-plus中图片预览插件源码改动
  • 原文地址:https://blog.csdn.net/qq_51409098/article/details/127769261