Java中的反射技术获取方法参数信息

Java的反射技术并不陌生,它的一个应用就是获取方法的参数信息。

1、传统方法获取参数类型

比较常用的方法就是通过Method的getParameterTypes()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package hello.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ReflectDemo {
public static void getMethodInfo(String className) {
try {
Class clazz = Class.forName(className);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
getMethodInfo(method);
}
} catch(Exception e) {
e.printStackTrace();
}
}

private static void getMethodInfo(Method method) {
System.out.println("method name: " + method.getName());
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> clas : parameterTypes) {
System.out.println("arg type: " + clas.getName());
}
System.out.println("---------------------------------");
}

public static void main(String[] args) {
getMethodInfo("java.lang.Object");
}
}

来看一下Java中所有类的祖先Object。输出信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
method name: wait
---------------------------------
method name: wait
arg type: long
arg type: int
---------------------------------
method name: wait
arg type: long
---------------------------------
method name: equals
arg type: java.lang.Object
---------------------------------
method name: toString
---------------------------------
method name: hashCode
---------------------------------
method name: getClass
---------------------------------
method name: notify
---------------------------------
method name: notifyAll
---------------------------------

可以看出,通过getParameterTypes()方法只能获取方法的参数类型,而无法获取参数名。

2、Java8新功能

在jdk8中一个新特性就是它可以将方法参数的院信息存储到编译玩的class文件中(JEP 118)。利用这个特性就可以用反射技术来获取更多方法的参数信息。

于是将程序改成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package hello.reflect;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ReflectDemo {
public static void getMethodInfo(String className) {
try {
Class clazz = Class.forName(className);
Method[] methods = clazz.getMethods();
for (Method method : methods) {
getMethodParametersinfo(method);
}
} catch(Exception e) {
e.printStackTrace();
}
}

private static void getMethodParametersinfo(Method method) {
System.out.println("method name: " + method.getName());
Parameter [] paramaters = method.getParameters();
for (Parameter paramater : paramaters) {
System.out.print("arg name: " + paramater.isNamePresent() + " " + paramater.getName());
System.out.println(", arg type: " + paramater.getParameterizedType());
}
System.out.println("---------------------------------");
}
public static void main(String[] args) {
getMethodInfo("java.lang.Object");
}
}

输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
method name: wait
---------------------------------
method name: wait
arg name: false arg0, arg type: long
arg name: false arg1, arg type: int
---------------------------------
method name: wait
arg name: false arg0, arg type: long
---------------------------------
method name: equals
arg name: false arg0, arg type: class java.lang.Object
---------------------------------
method name: toString
---------------------------------
method name: hashCode
---------------------------------
method name: getClass
---------------------------------
method name: notify
---------------------------------
method name: notifyAll
---------------------------------

可以看出,输出的参数名是从arg0开始一直增加,这显然不是我们想要的结果,比如Object类中的wait方法源码如下

1
2
3
public final void wait(long timeout, int nanos) throws InterruptedException {
//省略
}

我们想要的是timeout,nanos这两个参数名,显然输出的并不是。

原因就在于输出中的那个false,它是paramater.isNamePresent()方法的返回值,false说明了改class文件中并没有提供方法的参数名。也就是说,想要让class文件中存储方法的参数名称信息,需要在编译的时候显示的声明。

3、javac的parameters选项

显示声明的方式可以在编译命令的说明中找到,打开命令行,输入 javac -help:

1

上面说的明明白白,需要加入parameters参数才可以。

但是显然Object的类是无法重新编译的,于是是自己新建了一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package hello.reflect;

public class ClassA {

public ClassA() {}

public void addArrayOfStrings(String[] strings) {}

public void addInt(int count) {}

public void addString(String url, String argName) {}

public String toString(){
return "ClassA";
}
}

我用javac命令编译试了一下,加上parameters参数之后,class文件明显变大了几十k。

4、eclipse中加入parameters编译参数

在eclipse中开发的时候编译默认是不带这个参数的,也就是说用上面的方法输出我新建的ClassA,参数名肯定还是arg0这些,需要额外设置一下才可以。

具体方法就是右键工程,选择properties

2

把最下面那个选项选上,再重新编译一下,就可以了。

关于重新编译,这个问题也经常遇到,有时候如果少了某个class文件经常会输出找不到或无法加载主类的错误,这时候就需要重新编译了。选择最上面的project-clean,然后再选择要重新编译的工程。之后再次运行,就会重新编译。

这是再运行,将ClassA作为参数就可以得到下面的输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
method name: toString
---------------------------------
method name: addArrayOfStrings
arg name: true strings, arg type: class [Ljava.lang.String;
---------------------------------
method name: addString
arg name: true url, arg type: class java.lang.String
arg name: true argName, arg type: class java.lang.String
---------------------------------
method name: addInt
arg name: true count, arg type: int
---------------------------------
method name: wait
---------------------------------
method name: wait
arg name: false arg0, arg type: long
arg name: false arg1, arg type: int
---------------------------------
method name: wait
arg name: false arg0, arg type: long
---------------------------------
method name: equals
arg name: false arg0, arg type: class java.lang.Object
---------------------------------
method name: hashCode
---------------------------------
method name: getClass
---------------------------------
method name: notify
---------------------------------
method name: notifyAll
---------------------------------

现在就可以获取方法的参数名称了。

5、总结

这个功能看似很无聊,但是在实际开发中应用还是很广的 ,之所以记录下来就是因为最近在使用github上面一个cdp4j的库,下载到本地以后一直运行错误,最后排查原因才知道是这个原因。

这个功能在使用动态代理时用于获取参数信息还是很有用的。

6、参考地址

http://deepinmind.iteye.com/blog/2046050

http://www.cnblogs.com/zhangshiwen/p/6022794.html