您的位置:首页 > 编程语言 > Java开发

获得方法参数名的多种方法以及javassit的bug

2017-03-30 00:15 323 查看
今天碰到一个奇怪的现象,自己机子上单元测试没问题的一个接口,一放到服务器上参数就传不上去。经过排查,问题定位到获取方法参数名上。逻辑是这样的,通过方法中的参数名去解析json中相应的值,然后通过反射调用业务逻辑层。然而通过javassit获取方法的参数名非常不稳定,字节码解析经常解析出错。接下来详细谈谈如何获得方法参数名以及javassit的这个bug的具体表现。

字节码与编译器

本地变量表

在jvm中,LocalVariableTable本地变量表是methodInfo中的code属性中的一个属性。它保存了方法中的参数信息。它的字节码信息如下:

需要注意的是,编译器需要指定-g或者-g:vars参数。在myeclipse中通过如下选项




maven中需要对build插件maven-compiler-plugin设置debug或debugLevel。默认是true。

方法参数表

从jdk8开始,通过反射已经可以获得方法中的参数名,不需要再去通过操作字节码获得。它的实现方式是通过编译器把相应的参数信息放到方法参数表中。它的字节码如下:

需要注意的是,编译器需要指定-parameters参数,可以看一下jdk8的javac说明



在myeclipse需要勾选以下选项



在maven中,需要在build插件maven-compiler-plugin中的compile-Args参数中增加parameters参数。

获得方法参数名的方法

javassist

javassist是一个字节码操作框架,通过它可以方便完成反射无法做到的事情,并且效率比反射高。根据上面对字节码的说明,结合javassist的api可以很方便的实现获得方法参数名的方法。代码如下:

/**
*
* 通过javassit获取方法参数名,不能获得代理类的方法参数名。存在bug
*
* @param methodName
* @return
*/
protected List<String> getParamName(String methodName){
List<String> list=new ArrayList<>();
try{
ClassPool pool=ClassPool.getDefault();
ClassClassPath classPath=new ClassClassPath(this.getClass());
pool.insertClassPath(classPath);
CtClass cc=pool.get(this.clazz.getName());
CtMethod cm=cc.getDeclaredMethod(methodName);
MethodInfo methodInfo=cm.getMethodInfo();
CodeAttribute codeAttribute=methodInfo.getCodeAttribute();
LocalVariableAttribute attr=(LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
if(attr!=null){
int pos=Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
for(int i=0;i<cm.getParameterTypes().length;i++){
list.add(attr.variableName(i+pos));
}
}
}catch(Exception e){
e.printStackTrace();
}
return list;
}
该方式不能获取代理类的方法参数名,如果是代理类,需要解析出被代理的类。

bug

这种方式在javassit-3.18.1-GA.jar中存在bug,其他版本没有测试,不知是否也存在。bug表现在某些时候编译后,attr.variableName方法获得的参数名并不是指定方法的参数名,methodInfo的信息却是正确的。

ASM和spring

asm同样是操作字节码的框架,但是它的效率是最高的。spring提供了一个工具类,底层是通过asm实现的,可以用该工具类获得参数名,这里就只介绍spring的这个工具类。代码如下:

/**
*
* 通过spring获取方法参数名,底层通过ASM实现。不能获得代理类的方法参数名。
*
* @param methodName
* @return
*/
protected List<String> getParamNameBySpring(String methodName){
List<String> list=new ArrayList<>();
Method[] targetMs=this.clazz.getDeclaredMethods();
for(Method targetM : targetMs){
if(targetM.getName().equals(methodName)){
ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = parameterNameDiscoverer.getParameterNames(targetM);
Arrays.asList(parameterNames).forEach(p -> {
list.add(p);
});
}
}
return list;
}
该方式不能获取代理类的方法参数名,如果是代理类,需要解析出被代理的类。

jdk8

jdk8终于在反射中实现了获得方法的参数名的方法,原理在上文已经讲了,代码如下:

/**
*
* 通过jdk8的反射获取方法参数名,编译时需要添加参数-parameters。不能获得代理类的方法参数名。
*
* @param methodName
* @return
*/
protected List<String> getParamNameByJDK8(String methodName){
List<String> list=new ArrayList<>();
Method[] targetMs=this.clazz.getDeclaredMethods();
for(Method targetM : targetMs){
if(targetM.getName().equals(methodName)){
Arrays.asList(targetM.getParameters()).forEach(p -> {
list.add(p.getName());
});
}
}
return list;
}
该方式不能获取代理类的方法参数名,如果是代理类,需要解析出被代理的类。否则获得的方法名为arg0。

解析出被代理类

可以看到,这几种方法都不能适用于代理类,最后附带放上代理类如何解析出被代理类的方法吧。代码如下:

public class TargetUtils {

public static Object getTarget(Object proxy) throws Exception{

if(!AopUtils.isAopProxy(proxy)){
return proxy;
}

if(AopUtils.isJdkDynamicProxy(proxy)){
return getJdkDynamicProxyTargetObject(proxy);
}
else{
return getCglibProxyTargetObject(proxy);
}

}

private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception{

Field h=proxy.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy=(AopProxy) h.get(proxy);
Field advised=aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target=((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();

return target;

}

private static Object getCglibProxyTargetObject(Object proxy) throws Exception{

Field h=proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor=h.get(proxy);
Field advised=dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target=((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();

return target;

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java web java