普通情况下无法拿到方法的参数名
.java文件属于源码文件,它需要经过了javac编译器编译为.class字节码文件才能被JVM执行的
Java在编译的时候对于方法,默认是不会保留方法参数名,因此如果我们在运行期想从.class字节码里直接拿到方法的参数名是做不到的
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test.class.getMethod("test", String.class, Integer.class);
int parameterCount = method.getParameterCount();
Parameter[] parameters = method.getParameters();
System.out.println("方法参数总数:" + parameterCount);
Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
}
public void test(String name, Integer age) {
}
}
方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1
框架中好像能拿到参数名?
按照上面的例子,我们在运行期想从.class字节码里直接拿到方法的参数名是做不到的
但是联想起来在使用Spring MVC的时候,即使不使用注解,只要参数名和请求参数的key对应上了,就能自动完成数值的封装。
而在使用MyBatis(接口模式)时,接口方法向xml里的SQL语句传参时,必须(当然不是100%的必须,特殊情况此处不做考虑)使用@Param(’’)指定key值,在SQL中才可以取到
SpringMVC有什么秘技
public class Main {
public String testArgName(String name, Integer age) {
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = Main.class.getMethod("testArgName", String.class, Integer.class);
int parameterCount = method.getParameterCount();
Parameter[] parameters = method.getParameters();
System.out.println("方法参数总数:" + parameterCount);
Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
// MethodParameter,DefaultParameterNameDiscoverer,ParameterNameDiscoverer
// 都是org.springframework.core包下的类
MethodParameter nameParameter = new MethodParameter(method, 0);
MethodParameter ageParameter = new MethodParameter(method, 1);
ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
nameParameter.initParameterNameDiscovery(parameterNameDiscoverer);
ageParameter.initParameterNameDiscovery(parameterNameDiscoverer);
System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
}
}
方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1
class java.lang.String----name
class java.lang.Integer----age
Spring MVC
借助ParameterNameDiscoverer
完成了方法参数名的获取,进而完成数据封装
LineNumberTable
线上程序抛出异常时显示的行号,为啥就恰好就是你源码的那一行呢?有这疑问是因为JVM执行的是.class文件,而该文件的行和.java源文件的行肯定是对应不上的,为何行号却能在.java文件里对应上?
LineNumberTable属性存在于代码的字节码中, 它建立了字节码偏移量到源代码行号之间的联系
javap -verbose xxx.class查看字节码信息
依次执行javac xxx.java 和 javap -verbose xxx.class 查看字节码信息
方法中有个LineNumberTable,
可以看到,line
行号和源码处完全一样,这就解答了我们上面的行号对应的疑问了:LineNumberTable
它记录着在源代码处的行号。
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
Method method = Test.class.getMethod("test", String.class, Integer.class);
int parameterCount = method.getParameterCount();
Parameter[] parameters = method.getParameters();
System.out.println("方法参数总数:" + parameterCount);
Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
}
public void test(String name, Integer age) {
}
}
// 前方省略。。。
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=6, locals=4, args_size=1
0: ldc #2 // class com/example/demo/demos/Test
2: ldc #3 // String test
// 这块省略。。。
66: invokeinterface #20, 2 // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
71: return
LineNumberTable:
line 9: 0
line 10: 22
line 11: 27
line 13: 32
line 14: 57
line 15: 71
Exceptions:
throws java.lang.NoSuchMethodException
public void test(java.lang.String, java.lang.Integer);
// 这里省略。。。
LineNumberTable:
line 18: 0
}
// 后方省略
LocalVariableTable
LocalVariableTable
属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。这个属性也是存在于代码的字节码中
从名字可以看出来:它是局部变量的一个集合。描述了局部变量和描述符以及和源代码的对应关系。
依次执行javac xxx.java 和 javap -verbose xxx.class 查看字节码信息
可以看到有LineNumberTable,
并没有LocalVariableTable
改成分别使用javac -parameters、javac -g
来编译后再执行,运行发现程序中能够获得参数名了,查看字节码:
-parameters:
// 前方省略。。。
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=6, locals=4, args_size=1
0: ldc #2 // class com/example/demo/demos/Test
2: ldc #3 // String test
// 这块省略。。。
66: invokeinterface #20, 2 // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
71: return
LineNumberTable:
line 9: 0
line 10: 22
line 11: 27
line 13: 32
line 14: 57
line 15: 71
Exceptions:
throws java.lang.NoSuchMethodException
MethodParameters:
Name Flags
args
public void test(java.lang.String, java.lang.Integer);
descriptor: (Ljava/lang/String;Ljava/lang/Integer;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 18: 0
MethodParameters:
Name Flags
name
age
}
// 后方省略。。。
-g:
// 前方省略。。。
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=6, locals=4, args_size=1
0: ldc #2 // class com/example/demo/demos/Test
2: ldc #3 // String test
// 这块省略。。。
66: invokeinterface #20, 2 // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
71: return
LineNumberTable:
line 9: 0
line 10: 22
line 11: 27
line 13: 32
line 14: 57
line 15: 71
LocalVariableTable:
Start Length Slot Name Signature
0 72 0 args [Ljava/lang/String;
22 50 1 method Ljava/lang/reflect/Method;
27 45 2 parameterCount I
32 40 3 parameters [Ljava/lang/reflect/Parameter;
Exceptions:
throws java.lang.NoSuchMethodException
public void test(java.lang.String, java.lang.Integer);
descriptor: (Ljava/lang/String;Ljava/lang/Integer;)V
flags: ACC_PUBLIC
Code:
stack=0, locals=3, args_size=3
0: return
LineNumberTable:
line 18: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/example/demo/demos/Test;
0 1 1 name Ljava/lang/String;
0 1 2 age Ljava/lang/Integer;
}
// 后方省略。。。
这里多了一个LocalVariableTable
,即局部变量表
,就记录着我们方法入参的形参名字。既然记录着了,这样我们就可以通过分析字节码信息来得到这个名称了
获取方法参数名的方式
虽然Java编译器默认情况下会抹去方法的参数名,但有上面介绍了字节码的相关知识可知,我们还是有方法来得到方法的参数名的。
方法一:使用-parameters(最简单直接)
java8原生支持,直接通过java.lang.reflect.Parameter就能获取到
方法二:借助ASM(推荐)
它是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能,它能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
这样我们就可以通过ASM来分析字节码信息中的本地变量表获取方法参数名了。
总结
为什么我们使用的框架中,比如Spring MVC可以获取方法参数名
spring-core中有个ParameterNameDiscoverer
就是用来获取参数名的,底层用的是asm
解析,但是接口方法的参数名无法得到,即只能是非接口类的方法参数名可以
可是平时我们开发spring MVC项目都没有显式添加-g
编译参数Spring MVC是怎么获取方法参数的?因为,maven
默认是添加了-g
选项的
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<debug>true</debug> //默认,无需显式配置
<compilerArgument>-g</compilerArgument> //默认,无需显式配置
</configuration>
</plugin>
</plugins>
- 1.要想在运行时获取方法的参数名,首先class文件中得有参数名才可以
- 2.spring mvc底层是通过asm获取的字节码中的方法参数名
- 3.javac可以通过添加
-g
或者-parameter
选项在class文件中添加参数信息 - 4.maven编译插件默认添加了
-g
选项