[TOC]
之前我们已经介绍了 Java 中框架常用的技术 — 反射。可以这么说反射方便了我们的开发。今天我们来说说他的短板,或者说我们今天在反射的基础上在进行方便化。
反射操作方法
在上一章节中我们学会了通过反射去调用方法。
public class App {public void test(String str, Integer integer) {System.out.println(str);
System.out.println(integer);
}
}
这个时候如果我想获取 test 方法对象的话应该这么做
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。
对的,你没看错返回的居然是无意义的名称,arg0 , arg1. 这就奇怪了。至于为什么呢?我现在还不想告诉你。下面会慢慢告诉你。
Spring 的方法的优点
做过 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=5
这里我问一下你们有没有想过为什么 springmvc 它能够通过你传递的参数一一进行对应呢?我们上面已经尝试过通过反射是无法获取方法参数名称的。而 springmvc 无非就是反射操作方法的。这里是不是很神奇,不得不佩服 springmvc 的强大。强大到让人害怕。
反射如何实现 Spring 的方法
上面两个案例揭露了反射的缺点以及 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
- KotlinReflectionParameterNameDiscoverer:Spring5.0 提供,但是也得 jdk8 及以上版本使用
- StandardReflectionParameterNameDiscoverer:Spring4.0 提供,但是也得 jdk8 及以上版本使用
- LocalVariableTableParameterNameDiscoverer:Spring2.0 就有了,对 JDK 版本没啥要求,完全 Spring 自己实现的获取字段名称,逻辑复杂些,效率稍微低一点
总结一下就是在 springmvc4.0 之前 springmvc 都是通过自己实现的一套代码去获取字节码然后分析的。这里能力有限就不分析了。
在 4.0 以后因为 Java8 的推出弥补了这个 bug.springmvc 也就都采用 jdk 提供的功能获取参数名了。下面我们来看看 jdk8 是如何解决这个问题的。
Java 字节码
在上一节我们通过 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
javac 的彩蛋
续点
每日一笑
今天公司放假,和老婆商量好一起去她家,出发前夕,老婆说:给我家里人的礼物买好了么?然后我去卧室全搬出来了;给她说:这是你舅的,这是你叔的,这是你爷爷的,这是你奶奶的,这是你爸的,这是你妈的,这是…………然后我俩就打起来了!
上期答案
无
加入战队
<span id=”addMe”> 加入战队 </span>
微信公众号