你觉得反射真的无所不能?至少JDK8之后很强大

以前咱们已经介绍了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

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=5jvm

这里我问一下大家有没有想过为何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

而后咱们在经过命令查看下这个class文件
javap -verbose App.class
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);
    }
}

spring方法
对,就是ParameterNameDiscoverer这个方法帮助了咱们。这里简单说说ParameterNameDiscoverer做用。springmvc中会有一个默认的ParameterNameDiscoverer解释器DefaultParameterNameDiscoverer该类继承PrioritizedParameterNameDiscovererPrioritizedParameterNameDiscoverer这个类就是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 : 编译更加全面点
javacg
通过对比发现javac 和javac -g 的区别好像是javac -g 编译信息多出LocalVariableTable信息。

名称 解释
LineNumberTable 属性表存放方法的行号信息
LocalVariableTable   属性表中存放方法的局部变量信息

上图中经过javac -g 编译的信息中LocalVarableTable有三条数据,是由于在编译期间每一个非静态方法第一个参数都是this.去除第一条剩下的其实就是咱们须要的参数信息。可是咱们这个时候去执行一下看看效果。
对比

高级反射注意点

所谓的高级反射其实就是对jdk版本的要求,只要是jdk8的版本,就能够用jdk提供的parameter方法获取参数名了。在编译的时候须要加上 -parameters
编译

javac的彩蛋

彩蛋

续点

 每日一笑

今天公司放假,和老婆商量好一块儿去她家,出发前夕,老婆说:给我家里人的礼物买好了么?而后我去卧室全搬出来了;给她说:这是你舅的,这是你叔的,这是你爷爷的,这是你奶奶的,这是你爸的,这是你妈的,这是…………而后我俩就打起来了!

上期答案

加入战队

# 加入战队

微信公众号

微信公众号

相关文章
相关标签/搜索