第二十二讲-获取方法中的参数名
第二十二讲-获取方法中的参数名
这一讲我们看一下获取方法的参数名称。
要想获取参数名称,就要在参数解析组合器中加入一个参数名称解析器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()); // 支持基于本地变量表来获取方法参数名
}
}
分类:
Spring 高级49讲
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .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语句:使用策略模式优化代码结构