以前咱们已经介绍了Java中框架经常使用的技术---反射。能够这么说反射方便了咱们的开发。今天咱们来讲说他的短板,或者说咱们今天在反射的基础上在进行方便化。java
在上一章节中咱们学会了经过反射去调用方法。web
public class App { public void test(String str, Integer integer) { System.out.println(str); System.out.println(integer); } }
这个时候若是我想获取test方法对象的话应该这么作spring
Method testMethod = App.class.getMethod("test", String.class, Integer.class);
这里就不在赘述如何经过Method对象调用方法了。文章末尾会给出上一章节的地址。今天咱们要研究的是Method如何获取方法参数这一块。看似简单却又是那么的传奇。咱们看看下面一段代码执行的效果微信
public static void main(String[] args) throws ParseException, NoSuchMethodException { Method[] methods = App.class.getMethods(); Method testMethod = App.class.getMethod("test", String.class, Integer.class); Class<?>[] parameterTypes = testMethod.getParameterTypes(); Parameter[] parameters = testMethod.getParameters(); for (Parameter parameter : parameters) { System.out.println(parameter.getName()); } }
那么输出的两个参数名称是什么呢?一开始笔者这里想固然的认为是 str , name 。 相信此时的你应该和我同样认为是str , name 。mvc
对的,你没看错返回的竟然是无心义的名称 , arg0 , arg1.这就奇怪了。至于为何呢?我如今还不想告诉你。下面会慢慢告诉你。app
作过Javaweb开发的确定都用过spring,springmvc , 在写controller层的时候咱们都会在方法里直接写key值的名称,而后在请求地址中给相应的key赋值。框架
@RequestMapping(value = "/deptId", method = RequestMethod.GET) public PagedResult<SysDept> selectSysDeptsByPK(Integer pageNumber, Integer pageSize) { return sysDeptService.selectSysDeptsByPK(deptId, pageNumber, pageSize); }
上述的controller咱们在前端发送请求后会这样发送
http://{ip}:{port}/{projectName}/deptId?pageNumber=1&pageSize=5jvm
这里我问一下大家有没有想过为何springmvc它可以经过你传递的参数一一进行对应呢?咱们上面已经尝试过经过反射是没法获取方法参数名称的。而springmvc无非就是反射操做方法的。这里是否是很神奇,不得不佩服springmvc的强大。强大到让人惧怕。工具
上面两个案例揭露了反射的缺点以及springmvc的强大。这里须要借助springmvc提供的一个工具ParameterNameDiscoverer
。 这个类顾名思义就是发现参数名称。在使用这个类以前咱们先来了解下为何反射获取不到方法名称。
这里须要简单说说Java执行过程,Java之因此能够跨容器是由于Java针对各个系统提供了不一样jvm,因此咱们开发前都须要安装不一样版本的jdk,jdk里面提供了jvm,java 代码运行期间是经过jvm去操做class文件的。可是咱们平时都是开发java文件的。因此在jvm执行以前会有一个编译期间。javac就是用来变异java文件为class文件的。
package com.zxhtom.test; /** * Hello world! */ public class App { public void test(String str, Integer integer) { } }
针对上述代码咱们经过javac进行编译下试试看看效果。
javac App.java
编译完成以后会出现一个同名的class文件
而后咱们在经过命令查看下这个class文件
javap -verbose App.class
经过查看App.java对应的字节码发如今javac编译的时候对于方法的名称根本不会去记录的。想一想也对我执行方法的时候只须要按顺序将参数放进去就好了。根本不须要关心参数名称是什么。那么问题显而易见了jvm不须要参数名因此编译时过率了。可是咱们反射想经过参数名称一一对应这样效率更快。那么是springmvc是如何解决的呢。
private static final ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); public static void main(String[] args) throws ParseException, NoSuchMethodException { java.lang.reflect.Method testMethod = App.class.getMethod("test", String.class, Integer.class); String[] parameterNames = parameterNameDiscoverer.getParameterNames(testMethod); for (String parameterName : parameterNames) { System.out.println(parameterName); } }
对,就是ParameterNameDiscoverer
这个方法帮助了咱们。这里简单说说ParameterNameDiscoverer
做用。springmvc中会有一个默认的ParameterNameDiscoverer
解释器DefaultParameterNameDiscoverer
该类继承PrioritizedParameterNameDiscoverer
。PrioritizedParameterNameDiscoverer
这个类就是getParameterNames去获取方法名的。在springmvc中经过addDiscoverer
方法有三个类注册到PrioritizedParameterNameDiscoverer
总结一下就是在springmvc4.0以前springmvc都是经过本身实现的一套代码去获取字节码而后分析的。这里能力有限就不分析了。
在4.0之后由于Java8的推出弥补了这个bug.springmvc也就都采用jdk提供的功能获取参数名了。下面咱们来看看jdk8是如何解决这个问题的。
在上一节咱们经过javac , javap命令进行了Java的编译了查看。咱们发现class字节码中记录的信息有【常量区,类,方法】其中对于代码的记录有位置,堆,栈,行号等等。这也是咱们jvm调优的依据。可是这仅仅是咱们使用简单的javac的编译。
javac -g : 编译更加全面点
通过对比发现javac 和javac -g 的区别好像是javac -g 编译信息多出LocalVariableTable信息。
名称 | 解释 |
---|---|
LineNumberTable | 属性表存放方法的行号信息 |
LocalVariableTable | 属性表中存放方法的局部变量信息 |
上图中经过javac -g 编译的信息中LocalVarableTable有三条数据,是由于在编译期间每一个非静态方法第一个参数都是this.去除第一条剩下的其实就是咱们须要的参数信息。可是咱们这个时候去执行一下看看效果。
所谓的高级反射其实就是对jdk版本的要求,只要是jdk8的版本,就能够用jdk提供的parameter方法获取参数名了。在编译的时候须要加上 -parameters
今天公司放假,和老婆商量好一块儿去她家,出发前夕,老婆说:给我家里人的礼物买好了么?而后我去卧室全搬出来了;给她说:这是你舅的,这是你叔的,这是你爷爷的,这是你奶奶的,这是你爸的,这是你妈的,这是…………而后我俩就打起来了!
无