首頁>技術>

反射操作方法

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是如何解決的呢。

對,就是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的彩蛋

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

  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • redis需要部署在不同的機器上,怎麼配置讓別人連線