第二十二讲-获取方法中的参数名

第二十二讲-获取方法中的参数名

这一讲我们看一下获取方法的参数名称。

要想获取参数名称,就要在参数解析组合器中加入一个参数名称解析器DefaultParameterNameDiscoverer,所以参数名的获取并不是那么简单的。下面我们来探讨一下:

我们写一个简单一点的Java类,然后呢,手动编译这个类,不要用IDEA编译,因为IDEA编译时会加入很多参数

public class Bean2 {
    public void foo(String name, int age){
        
    }
}

使用javac命令编译得到字节码再交给IDEA反编译查看:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cherry.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String var1, int var2) {
    }
}

我们发现,编译后的参数没有了!参数名丢了,变成了var1, var2。

那么怎么才能让参数名记录到class文件中呢?

第一个办法就是在编译时加上parameters选项,这个选项就会把方法的参数名记录下来存到class文件里,将来可以通过反射调用掉这个参数名字

javac -parameters Bean.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cherry.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String name, int age) {
    }
}

我们发现此时class文件中方法中的参数名就有了。

那么原理是什么呢?我们用javap来反编译一下没有方法参数名的情形:

javac Bean.java
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cherry.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String var1, int var2) {
    }
}

反编译一下该class

javap -c -v 
public class com.cherry.a22.Bean2
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #7                          // com/cherry/a22/Bean2
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // com/cherry/a22/Bean2
   #8 = Utf8               com/cherry/a22/Bean2
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               foo
  #12 = Utf8               (Ljava/lang/String;I)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Bean2.java
{
  public com.cherry.a22.Bean2();
    descriptor: ()V
    flags: (0x0001) 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);	// 我们发现foo方法里只有参数类型信息,没有和参数名有关的信息
    descriptor: (Ljava/lang/String;I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=0, locals=3, args_size=3
         0: return
      LineNumberTable:
        line 6: 0
}

接下来我们看看带有 paramteres参数编译后的字节码文件

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cherry.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String name, int age) {
    }
}
public class com.cherry.a22.Bean2
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #7                          // com/cherry/a22/Bean2
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // com/cherry/a22/Bean2
   #8 = Utf8               com/cherry/a22/Bean2
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               foo
  #12 = Utf8               (Ljava/lang/String;I)V
  #13 = Utf8               MethodParameters
  #14 = Utf8               name
  #15 = Utf8               age
  #16 = Utf8               SourceFile
  #17 = Utf8               Bean2.java
{
  public com.cherry.a22.Bean2();
    descriptor: ()V
    flags: (0x0001) 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: (0x0001) ACC_PUBLIC
    Code:
      stack=0, locals=3, args_size=3
         0: return
      LineNumberTable:
        line 6: 0
    MethodParameters:	// 我们发现多了一组信息: MethodParameters, 这些信息记录了我们的参数名字,且参数顺序和我们定义的顺序一致
      Name                           Flags
      name
      age
}

上面就是一种保留参数名的办法。除此以外还有一种就是加-g的参数:

 javac -g .\Bean2.java 
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.cherry.a22;

public class Bean2 {
    public Bean2() {
    }

    public void foo(String name, int age) {
    }
}
{
  public com.cherry.a22.Bean2();
    descriptor: ()V
    flags: (0x0001) 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/cherry/a22/Bean2;

  public void foo(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=0, locals=3, args_size=3
         0: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:		// 我们发现-g参数同样也能保留方法参数,但是参数所保留的位置不一样,-g并不会生成MethodParameters信息,而是存放在本地								// 变量表中
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/cherry/a22/Bean2;
            0       1     1  name   Ljava/lang/String;
            0       1     2   age   I
}

这两者有什么区别吗?

  • 对于使用-paramteres参数编译后的字节码记录方法参数的信息,我们可以通过反射API来获取到的。(存在methodParameters中)
  • 对于使用-g参数编译后的字节码记录方法参数的信息,反射式取不到的,但是我们可以通过ASM获取到。(存在本地变量表中)

对于Spring框架来讲,这两种方式Spring是同时支持的。我们来演示一下。

注意,在演示前,我们得将Bean2.java文件放在包的外面,避免IDEA自动编译加上一些奇怪的参数。

首先是反射获取参数名称:

public class A22 {
    public static void main(String[] args) throws NoSuchMethodException {
        Method foo = Bean2.class.getDeclaredMethod("foo", String.class, int.class);
        for (Parameter parameter : foo.getParameters()) {
            System.out.println(parameter);
        }
    }
}
java.lang.String name
int age

接下来我们演示使用ASM技术来获取方法中的参数名称:

public class A22 {
    public static void main(String[] args) throws NoSuchMethodException {
        Method foo = Bean2.class.getDeclaredMethod("foo", String.class, int.class);
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(foo);
        for(String parameterName: parameterNames){
            System.out.println(parameterName);
        }
    }
}
name
age

最后呢,我们看一下Spring提供的方法参数名解析器的一个实现类的相关源代码:

public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {

	public DefaultParameterNameDiscoverer() {
		// TODO Remove this conditional inclusion when upgrading to Kotlin 1.5, see https://youtrack.jetbrains.com/issue/KT-44594
		if (KotlinDetector.isKotlinReflectPresent() && !NativeDetector.inNativeImage()) {
			addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
		}
		addDiscoverer(new StandardReflectionParameterNameDiscoverer());	// 支持基于反射获取方法参数名
		addDiscoverer(new LocalVariableTableParameterNameDiscoverer());	// 支持基于本地变量表来获取方法参数名
	}
}
posted @   LilyFlower  阅读(6)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示