你以為反射真的不能為所欲為?至少JDK8以後很強大

反射操作方法

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 。

你以為反射真的不能為所欲為?至少JDK8以後很強大

對的,你沒看錯返回的居然是無意義的名稱 , 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);
}
/<sysdept>

上述的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文件

你以為反射真的不能為所欲為?至少JDK8以後很強大

然後我們在通過命令查看下這個class文件

javap -verbose App.class

你以為反射真的不能為所欲為?至少JDK8以後很強大

通過查看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);
}
}
你以為反射真的不能為所欲為?至少JDK8以後很強大

對,就是ParameterNameDiscoverer這個方法幫助了我們。這裡簡單說說ParameterNameDiscoverer作用。springmvc中會有一個默認的ParameterNameDiscoverer解釋器DefaultParameterNameDiscoverer該類繼承PrioritizedParameterNameDiscoverer。PrioritizedParameterNameDiscoverer這個類就是getParameterNames去獲取方法名的。在springmvc中通過addDiscoverer方法有三個類註冊到PrioritizedParameterNameDiscoverer

你以為反射真的不能為所欲為?至少JDK8以後很強大

  • 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 : 編譯更加全面點

你以為反射真的不能為所欲為?至少JDK8以後很強大

經過對比發現javac 和javac -g 的區別好像是javac -g 編譯信息多出LocalVariableTable信息。

名稱 解釋 LineNumberTable 屬性表存放方法的行號信息 LocalVariableTable   屬性表中存放方法的局部變量信息 上圖中通過javac -g 編譯的信息中LocalVarableTable有三條數據,是因為在編譯期間每個非靜態方法第一個參數都是this.去除第一條剩下的其實就是我們需要的參數信息。但是我們這個時候去執行一下看看效果。

你以為反射真的不能為所欲為?至少JDK8以後很強大

高級反射注意點

所謂的高級反射其實就是對jdk版本的要求,只要是jdk8的版本,就可以用jdk提供的parameter方法獲取參數名了。在編譯的時候需要加上 -parameters

你以為反射真的不能為所欲為?至少JDK8以後很強大

javac的彩蛋

你以為反射真的不能為所欲為?至少JDK8以後很強大

原文:https://www.cnblogs.com/zhangxinhua/p/11543653.html


分享到:


相關文章: