获得方法参数名的多种方法以及javassit的bug
2017-03-30 00:15
323 查看
今天碰到一个奇怪的现象,自己机子上单元测试没问题的一个接口,一放到服务器上参数就传不上去。经过排查,问题定位到获取方法参数名上。逻辑是这样的,通过方法中的参数名去解析json中相应的值,然后通过反射调用业务逻辑层。然而通过javassit获取方法的参数名非常不稳定,字节码解析经常解析出错。接下来详细谈谈如何获得方法参数名以及javassit的这个bug的具体表现。
字节码与编译器
需要注意的是,编译器需要指定-g或者-g:vars参数。在myeclipse中通过如下选项
![](http://img.blog.csdn.net/20170329234403704?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VkdXlpc2h1YWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
、
maven中需要对build插件maven-compiler-plugin设置debug或debugLevel。默认是true。
需要注意的是,编译器需要指定-parameters参数,可以看一下jdk8的javac说明
![](http://img.blog.csdn.net/20170329235304607?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VkdXlpc2h1YWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在myeclipse需要勾选以下选项
![](http://img.blog.csdn.net/20170329235403186?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3VkdXlpc2h1YWk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在maven中,需要在build插件maven-compiler-plugin中的compile-Args参数中增加parameters参数。
解析出被代理类
可以看到,这几种方法都不能适用于代理类,最后附带放上代理类如何解析出被代理类的方法吧。代码如下:
字节码与编译器
本地变量表
在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 APi之捕获请求原始内容的实现方法以及接受POST请求多个参数多种解决方案(十四)
- Spring MVC 集成 AOP,自定义注解,在切面获得方法参数,以及自定义注解的参数。
- 用javascript获得地址栏参数的两种方法
- window.open使用方法以及参数说明
- window.open使用方法以及参数说明
- Thin的DateChooser代码学习(关于js的函数参数为一个完整的函数以及“对象不支持此属性或方法”错误的解决)(原创,转载请声明)
- window.open的例子和使用方法以及参数说明(完整版)
- 关于对象数据库 DB4O 的一些BUG以及如何应对的方法
- window.open使用方法以及参数说明
- window.open的例子和使用方法以及参数说明
- window.open的例子和使用方法以及参数说明(完整版)
- window.open的使用方法以及参数说明
- [导入]在含有Request.QueryString页面中处理未能获得参数的错误处理方法
- 用jspSmartUpload上传文件同时获得参数的方法。
- window.open的例子和使用方法以及参数说明(完整版)
- window.open的例子和使用方法以及参数说明
- 用javascript获得地址栏参数的两种方法
- 获得当前页面的绝对的Url(可能的https,端口,以及参数)
- showModelessDiablog()的参数回调,以及父级页面元素引用的方法