代码中如何获得 Java 方法的形式参数名
对于一个 Java 方法 foo(int id, String name); 我们如何能在代码中获得形式参数名 id 和 name 呢?
我们知道通过反射 API
对于自己写的类,有两种办法获得形式参数名,分别是
1) Java8 的 -parameters 编译参数,然后用 Java8 新引入的反射 API Parameter
我们先在 Java8 下运行下面的代码
得到结果是
false
int: arg0
false
String: arg1
默认是也是得不到参数名的,isNamePresent() 是 false
如果编译的时候带上 -parameters 参数,再执行上面的代码结果就是
true
int: id
true
String: name
注:加不加编译参数 -parameters 产生的字节码用 javap -c 去反编译是看不出分别的,但是二进制码确实不一样的。看这里 Obtaining Names of Method Parameters. 现面一个对比,分别是没有加和加了 -parameters 编译出来的结果,直接用 vi 就能看出不同
上半部分是没有加 -parameters 编译参数产生的字节码,下半部是加了 -parameters 编译参数产生的字节码,注意看 foo() 方法部分,有 -parameters 则保留了形式参数名称在字节码中。
上面的对比结果同时也告诉了我 javap -c 并不是字节码的全部,javap -c 看到的相同也不意味着执行行为是一致的。
2) Play1 中的 play.utils.Java 的方法 public static String[] parameterNames(Method method)
这种方式值得参数一下,它用到了 javaassist 和 bytecodeparser 两个工具包,并依赖于 Play1 的编译方式,Play2 中已不适用。这里只列出它的源代码实现,已注释部分也在
摘自:play.utils.Java
上面有个 play.classloading.enhancers.LVEnhancer, 大致看下用到的两个方法
摘自:play.classloading.enhancers.LVEnhancer
其他:在 Play1 的世界里因为接管了编译器的某些活,所以它似乎还有办法获得实际传入方法的变量名,如在调用方法 foo() 时使用
int someId = 123;
String someName = "http://unmi.cc";
foo(someId, someName)
在代码中可以把 someId 和 someName 这两个名字得到
因为依据 Play1 对模板的调用
Detail orderDetail = ....
renderTemplate("details.html", orderDetail);
到模板文件 details.html 里就能用 ${orderDetail.id} 那样来访问 orderDetail 了,Java 代码中的变量名 orderDetail 和模板中的 orderDetail 必须对应的。
在 Play2 中开始使用 ParaNamer 来得到方法参数名称,见 https://github.com/paul-hammant/paranamer
更有趣的是编译时用
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
我们知道通过反射 API
Method.getGenericParameterTypes() 可以获得方法的参数类型,但是对于参数名一般就是 arg0, arg1, arg2 ..., 因为 Java 编译时把形式参数名擦除了。所以对完全擦除了形式参数名的字节码应该是没办法了,但我们自己写的类还是有能力去管控的。对于自己写的类,有两种办法获得形式参数名,分别是
1) Java8 的 -parameters 编译参数,然后用 Java8 新引入的反射 API Parameter
我们先在 Java8 下运行下面的代码
1package cc.unmi;
2
3import java.lang.reflect.Method;
4import java.lang.reflect.Parameter;
5
6public class MethodParameterNames {
7
8 public static void main(String[] args) throws Exception{
9 Method method = MethodParameterNames.class.getDeclaredMethod("foo", Integer.TYPE, String.class);
10 Parameter[] parameters = method.getParameters();
11 for(Parameter parameter: parameters) {
12 System.out.println(parameter.isNamePresent());
13 System.out.println(parameter.getType().getSimpleName() +": " + parameter.getName());
14 }
15 }
16
17 public void foo(int id, String name) {
18
19 }
20}得到结果是
false
int: arg0
false
String: arg1
默认是也是得不到参数名的,isNamePresent() 是 false
如果编译的时候带上 -parameters 参数,再执行上面的代码结果就是
true
int: id
true
String: name
注:加不加编译参数 -parameters 产生的字节码用 javap -c 去反编译是看不出分别的,但是二进制码确实不一样的。看这里 Obtaining Names of Method Parameters. 现面一个对比,分别是没有加和加了 -parameters 编译出来的结果,直接用 vi 就能看出不同
上半部分是没有加 -parameters 编译参数产生的字节码,下半部是加了 -parameters 编译参数产生的字节码,注意看 foo() 方法部分,有 -parameters 则保留了形式参数名称在字节码中。上面的对比结果同时也告诉了我 javap -c 并不是字节码的全部,javap -c 看到的相同也不意味着执行行为是一致的。
2) Play1 中的 play.utils.Java 的方法 public static String[] parameterNames(Method method)
这种方式值得参数一下,它用到了 javaassist 和 bytecodeparser 两个工具包,并依赖于 Play1 的编译方式,Play2 中已不适用。这里只列出它的源代码实现,已注释部分也在
摘自:play.utils.Java
1 /**
2 * Retrieve parameter names of a method
3 */
4public static String[] parameterNames(Method method) throws Exception {
5 try {
6 /*System.out.println("searching for " + "$" + method.getName() + LVEnhancer.computeMethodHash(method.getParameterTypes()));
7 for(Field f : method.getDeclaringClass().getDeclaredFields()) {
8 System.out.println(f.getName() + " : " + Modifier.toString(f.getModifiers()));
9 }
10 for(Field f : method.getDeclaringClass().getFields()) {
11 System.out.println(f.getName() + " : " + Modifier.toString(f.getModifiers()));
12 }*/
13 return (String[]) method.getDeclaringClass().getDeclaredField("$" + method.getName() + LVEnhancer.computeMethodHash(method.getParameterTypes())).get(null);
14 } catch (Exception e) {
15 throw new UnexpectedException("Cannot read parameter names for " + method, e);
16 }
17}上面有个 play.classloading.enhancers.LVEnhancer, 大致看下用到的两个方法
摘自:play.classloading.enhancers.LVEnhancer
1public static Integer computeMethodHash(Class<?>[] parameters) {
2 String[] names = new String[parameters.length];
3 for (int i = 0; i < parameters.length; i++) {
4 Class<?> param = parameters[i];
5 names[i] = "";
6 if (param.isArray()) {
7 int level = 1;
8 param = param.getComponentType();
9 // Array of array
10 while (param.isArray()) {
11 level++;
12 param = param.getComponentType();
13 }
14 names[i] = param.getName();
15 for (int j = 0; j < level; j++) {
16 names[i] += "[]";
17 }
18 } else {
19 names[i] = param.getName();
20 }
21 }
22 return computeMethodHash(names);
23}
24
25public static Integer computeMethodHash(String[] parameters) {
26 StringBuffer buffer = new StringBuffer();
27 for (String param : parameters) {
28 buffer.append(param);
29 }
30 Integer hash = buffer.toString().hashCode();
31 if (hash < 0) {
32 return -hash;
33 }
34 return hash;
35}其他:在 Play1 的世界里因为接管了编译器的某些活,所以它似乎还有办法获得实际传入方法的变量名,如在调用方法 foo() 时使用
int someId = 123;
String someName = "http://unmi.cc";
foo(someId, someName)
在代码中可以把 someId 和 someName 这两个名字得到
因为依据 Play1 对模板的调用
Detail orderDetail = ....
renderTemplate("details.html", orderDetail);
到模板文件 details.html 里就能用 ${orderDetail.id} 那样来访问 orderDetail 了,Java 代码中的变量名 orderDetail 和模板中的 orderDetail 必须对应的。
在 Play2 中开始使用 ParaNamer 来得到方法参数名称,见 https://github.com/paul-hammant/paranamer
更有趣的是编译时用
javac -g:vars TestLocalVarNames.java,然后用 javap -l 可以查看到变量名列表 LocalVariableTable。
永久链接 https://yanbin.blog/retrieve-java-method-formal-parameter-names/, 来自 隔叶黄莺 Yanbin's Blog[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。