论坛首页 Java企业应用论坛

获取方法参数名称

浏览 13792 次
精华帖 (10) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-05-02   最后修改:2010-05-02
struts2让人感觉很别扭的一个地方:由于Action中的方法都是无参数的,真实的参数要么写在Action的实例变量中,要么从request中读取。
如果一个Action只写一个命令,这还没有什么问题,但为了避免Action类数量的膨胀,我们一般将一个CRUD操作都放在同一个Action类中,用action!method的方式来访问。这时候严重影响方法的可读性。不读完代码通常不知道某个Action的方法需要哪些参数传递给它。
action方法的返回值也存在同样问题。

有时候在想,如果struts2的拦截器能自动选择要执行的方法,并且按照方法的参数名称名称来匹配方法参数就好了。这儿的问题是如何获取到方法参数名称。

好,下面我们来看一下怎么获取方法的参数名称。

我们知道,仅仅从class运行的角度,方法参数名称是没有必要的,方法参数类似局部变量一样,也是按照在局部变量表的偏移位置进行访问。为了调试方便(?)javac在编译时可选将方法参数以及局部变量名称存放在class文件中对应方法的局部变量表LocalVariableTable中。

我们看一个不包含局部变量表的class文件的结构:

再看一下包含局部变量表的class文件结构:

要控制是否生成局部变量表,用javac –g参数选项:

在Eclipse中这样设置:


接下来借助于一款字节码工具来读取局部变量表信息。Javassist是东京工业大学Shigeru Chiba编写的开源的字节码工具包,相对于ASM,性能稍低,但可读性更好,如果用Struts2的话,它已经被包含在xwork-core.jar文件中,所以不需要额外导入了。
package my;

import java.util.Arrays;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;

@SuppressWarnings("unchecked")
public class Hello {

	public static void main(String[] args) throws Exception {
		// 匹配静态方法
		String[] paramNames = getMethodParamNames(Hello.class, "main", String[].class);
		System.out.println(Arrays.toString(paramNames));
		// 匹配实例方法
		paramNames = getMethodParamNames(Hello.class, "foo", String.class);
		System.out.println(Arrays.toString(paramNames));
		// 自由匹配任一个重名方法
		paramNames = getMethodParamNames(Hello.class, "getMethodParamNames");
		System.out.println(Arrays.toString(paramNames));
		// 匹配特定签名的方法
		paramNames = getMethodParamNames(Hello.class, "getMethodParamNames", Class.class, String.class);
		System.out.println(Arrays.toString(paramNames));
	}

	/**
	 * 获取方法参数名称,按给定的参数类型匹配方法
	 * 
	 * @param clazz
	 * @param method
	 * @param paramTypes
	 * @return
	 * @throws NotFoundException
	 *             如果类或者方法不存在
	 * @throws MissingLVException
	 *             如果最终编译的class文件不包含局部变量表信息
	 */
	public static String[] getMethodParamNames(Class clazz, String method, Class... paramTypes)
			throws NotFoundException, MissingLVException {

		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get(clazz.getName());
		String[] paramTypeNames = new String[paramTypes.length];
		for (int i = 0; i < paramTypes.length; i++)
			paramTypeNames[i] = paramTypes[i].getName();
		CtMethod cm = cc.getDeclaredMethod(method, pool.get(paramTypeNames));
		return getMethodParamNames(cm);
	}

	/**
	 * 获取方法参数名称,匹配同名的某一个方法
	 * 
	 * @param clazz
	 * @param method
	 * @return
	 * @throws NotFoundException
	 *             如果类或者方法不存在
	 * @throws MissingLVException
	 *             如果最终编译的class文件不包含局部变量表信息
	 */
	public static String[] getMethodParamNames(Class clazz, String method) throws NotFoundException, MissingLVException {

		ClassPool pool = ClassPool.getDefault();
		CtClass cc = pool.get(clazz.getName());
		CtMethod cm = cc.getDeclaredMethod(method);
		return getMethodParamNames(cm);
	}

	/**
	 * 获取方法参数名称
	 * 
	 * @param cm
	 * @return
	 * @throws NotFoundException
	 * @throws MissingLVException
	 *             如果最终编译的class文件不包含局部变量表信息
	 */
	protected static String[] getMethodParamNames(CtMethod cm) throws NotFoundException, MissingLVException {
		CtClass cc = cm.getDeclaringClass();
		MethodInfo methodInfo = cm.getMethodInfo();
		CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
		LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
		if (attr == null)
			throw new MissingLVException(cc.getName());

		String[] paramNames = new String[cm.getParameterTypes().length];
		int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
		for (int i = 0; i < paramNames.length; i++)
			paramNames[i] = attr.variableName(i + pos);
		return paramNames;
	}

	/**
	 * 在class中未找到局部变量表信息<br>
	 * 使用编译器选项 javac -g:{vars}来编译源文件
	 * 
	 * @author Administrator
	 * 
	 */
	public static class MissingLVException extends Exception {
		static String msg = "class:%s 不包含局部变量表信息,请使用编译器选项 javac -g:{vars}来编译源文件。";

		public MissingLVException(String clazzName) {
			super(String.format(msg, clazzName));
		}
	}

	static void foo() {
	}

	void foo(String bar) {
	}
}


运行结果如下:
[args]
[bar]
[clazz, method, paramTypes]
[clazz, method]
  • 大小: 46.9 KB
  • 大小: 51.1 KB
  • 大小: 10.3 KB
  • 大小: 64 KB
   发表时间:2010-05-02  
看了题目,正是困扰我很久的问题!网上也没找到答案!
先投精华票,再细看,待会再评!
0 请登录后投票
   发表时间:2010-05-02  
请问一下,上面那个打开Class文件的那个是什么软件?

原来Java没有API可以解析出方法参数名的,难怪我一直找不到。也就是你说的“方法参数类似局部变量一样……”,从外面自然解析不出方法内的局部变量。

Struts、Hibernate这些框架下都有javassist这个jar包,原来这么有用!有空得研究下。
0 请登录后投票
   发表时间:2010-05-02  
rongxh2010 写道
请问一下,上面那个打开Class文件的那个是什么软件?


用的是jclasslib,网址在这儿:
http://www.ej-technologies.com/products/jclasslib/overview.html

看了一下jclasslib的源码,它在处理字节码方面的结构和javassist非常相似。
0 请登录后投票
   发表时间:2010-05-04  
受到你的启发,想到spring有这样的实现,有三个实现类:
有空在具体看看:
AspectJAdviceParameterNameDiscoverer, LocalVariableTableParameterNameDiscoverer,
PrioritizedParameterNameDiscoverer
0 请登录后投票
   发表时间:2010-05-05  
spring中的LocalVariableTableParameterNameDiscoverer更好用点
0 请登录后投票
   发表时间:2010-05-05  
http://paranamer.codehaus.org/
0 请登录后投票
   发表时间:2010-05-06  
melin 写道
受到你的启发,想到spring有这样的实现,有三个实现类:
有空在具体看看:
AspectJAdviceParameterNameDiscoverer, LocalVariableTableParameterNameDiscoverer,
PrioritizedParameterNameDiscoverer


看来Spring中还有好多没发现的东东,呵呵
0 请登录后投票
   发表时间:2010-05-06  
mycybyb 写道
http://paranamer.codehaus.org/


这个也不错,通过修改编译后的class文件,把参数名作为元数据保存下来,使得不管编译选项是否保留了LocalVariableTable,都可以获取到方法参数名。

有时间再细看一下。
thanks
0 请登录后投票
   发表时间:2010-06-18  
java的class能否转换为CtClass?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics