你覺得反射真的無所不能?至少JDK8之後很強大

[TOC]前端

以前咱們已經介紹了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如何獲取方法參數這一塊。看似簡單卻又是那麼的傳奇。咱們看看下面一段代碼執行的效果bash

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.這就奇怪了。至於爲何呢?我如今還不想告訴你。下面會慢慢告訴你。mvc

Spring的方法的優勢

作過Javaweb開發的確定都用過spring,springmvc , 在寫controller層的時候咱們都會在方法裏直接寫key值的名稱,而後在請求地址中給相應的key賦值。app

@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的強大。強大到讓人懼怕。jvm

反射如何實現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的彩蛋

彩蛋

續點

## 每日一笑

今天公司放假,和老婆商量好一塊兒去她家,出發前夕,老婆說:給我家裏人的禮物買好了麼?而後我去臥室全搬出來了;給她說:這是你舅的,這是你叔的,這是你爺爺的,這是你奶奶的,這是你爸的,這是你媽的,這是…………而後我倆就打起來了!

上期答案

微信公衆號

微信公衆號
相關文章
相關標籤/搜索