通过反射获得方法的参数信息
JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。
为什么javac不默认生成这些信息呢?它不生成我们怎么去获取?
ps:下面提到的LocalVariableTable是一个用于存储你java代码里声明的变量和JVM局部变量表中Slot对应关系的一个数据结构,大家不必太纠结它到底是什么。
java编译器默认不生成这些额外的信息(这些信息是指JVM正常运行并非必须要知道的信息),因为它们会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且事实上有很多信息java编译器并不会为我们生成,比如LocalVariableTable,java编译器就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也不是JVM运行时必须的,因此也需要你显式的告诉编译器来生成,需要加上-parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。
public class ByteCodeParameters {
public String simpleMethod(String canUGetMyName, Object yesICan) {
return "9527";
}
}
先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters:
//只截取了部分信息
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: ldc #2 // String 9527
2: areturn
LineNumberTable:
line 5: 0
//这个方法的描述到这里就结束了
接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息:
public java.lang.String simpleMethod(java.lang.String, java.lang.Object);
descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=3, args_size=3
0: ldc #2 // String 9527
2: areturn
LineNumberTable:
line 8: 0
MethodParameters:
Name Flags
canUGetMyName
yesICan
可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。
下面看一下在IntelliJ IDEA里运行的这个例子,我们试一下通过反射获取方法名 :
public class ByteCodeParameters {
public String simpleMethod(String canUGetMyName, Object yesICan) {
return "9527";
}
public static void main(String[] args) throws NoSuchMethodException {
Class<?> clazz = ByteCodeParameters.class;
Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class);
Parameter[] parameters = simple.getParameters();
for (Parameter p : parameters) {
System.out.println(p.getName());
}
}
}
输出 :
arg0
arg1
???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的file->settings里的Java Compiler选项,在
Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自
己编译一个.class文件放在IDEA的out下,然后再来运行 :
输出 :
canUGetMyName
yesICan
这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 官方文档
总结与补充
在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息。在SpringFramework里面也有一个ParameterNameDiscoverer工具接口可以获取方法参数名信息,在IoC容器创建Bean的过程中使用到了该类。