溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java怎么通過反射獲得方法真實(shí)參數(shù)名

發(fā)布時(shí)間:2022-01-26 14:14:21 來源:億速云 閱讀:351 作者:zzz 欄目:開發(fā)技術(shù)

今天小編給大家分享一下java怎么通過反射獲得方法真實(shí)參數(shù)名的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

示例

咱們先來看這樣一個(gè)小的demo:

java怎么通過反射獲得方法真實(shí)參數(shù)名

這是一個(gè)很簡(jiǎn)單的小demo,里面就是一個(gè)簡(jiǎn)簡(jiǎn)單單的類Test1,Test1有一個(gè)包含兩個(gè)參數(shù)的方法test,在Test1main方法中通過射來獲得test方法的所有參數(shù)的名字,并將其輸出到標(biāo)準(zhǔn)流。我本以為這個(gè)demo的運(yùn)行結(jié)果會(huì)得到方法的參數(shù)名。

結(jié)果

java怎么通過反射獲得方法真實(shí)參數(shù)名

驚不驚喜,意不意外?和說好的不一樣?。?/p>

咱們先停一下,先把為什么反射沒有拿到正確的值放到一邊,先說說我為什么要研究“通過反射原理獲得方法參數(shù)的實(shí)際名稱”這件事呢:是因?yàn)槲蚁敕抡詹?shí)現(xiàn)Spring MVC中的“自動(dòng)綁定”功能。大家知道Spring MVC里有一個(gè)“自動(dòng)綁定”的功能,能夠自動(dòng)綁定請(qǐng)求參數(shù)的值到@RequestMapping方法的參數(shù)上的,而不用任何額外的操作。

java怎么通過反射獲得方法真實(shí)參數(shù)名

這個(gè)功能我覺得很方便,所以我想嘗試自己仿造這個(gè)功能,然后用在公司的項(xiàng)目開發(fā)中。我猜測(cè)Spring是通過反射獲得方法的參數(shù)名后根據(jù)參數(shù)名到requestgetParam(String name)來獲得實(shí)際的值然后綁定的。因此我就嘗試著按照這個(gè)思路做,結(jié)果就遇到了上邊提到的反射獲得不了參數(shù)實(shí)際名稱的問題。我將這個(gè)問題請(qǐng)教了老大,老大了解到我的意圖后,經(jīng)過驗(yàn)證,得出結(jié)論:Spring MVC能不能正常使用自動(dòng)綁定是與java編譯器編譯時(shí)加不加-g參數(shù)有關(guān)的,而這個(gè)-g參數(shù)是代表著java編譯器在編譯時(shí)是否會(huì)輸出調(diào)試信息。

調(diào)試

java怎么通過反射獲得方法真實(shí)參數(shù)名

其實(shí)也就是說:Spring是通過讀取java編譯器生成的調(diào)試信息從而獲得的方法中參數(shù)的真實(shí)名稱的。說到這里,這個(gè)問題基本也解決了,但是我還是想再多說一點(diǎn)我后續(xù)的學(xué)習(xí)結(jié)果。后續(xù)我研究了一下Spring對(duì)于方法參數(shù)這塊的處理邏輯,也就是對(duì)于“自動(dòng)綁定”功能的底層的實(shí)現(xiàn)。

那么,Spring 到底是用了什么“黑科技”來做到獲得方法實(shí)際參數(shù)名的呢,咱們不妨就看Spring的源碼吧,看看Spring到底是如何實(shí)現(xiàn)的。

Spring源碼

Spring海量的源代碼,從何看起呢,這里,我是這樣解決的:我大體知道這個(gè)獲得方法實(shí)際參數(shù)名的操作應(yīng)當(dāng)和MethodgetParameters()方法有關(guān),或者說它的方法里或許會(huì)調(diào)用到這個(gè)方法,那么好了,我們可以使用idea提供的“查看調(diào)用?!钡墓δ?,來順藤摸瓜,看看在Spring中有沒有調(diào)用到這個(gè)方法,如果有,那么解決方案應(yīng)當(dāng)就在調(diào)用方法的附近。

java怎么通過反射獲得方法真實(shí)參數(shù)名

我們可以看到,果不其然,在調(diào)用棧里就有org.spring包中的方法,其中有兩個(gè)方法都是StandardReflectionParameterNameDiscoverer類的方法,其實(shí)我們已經(jīng)找到了,看這個(gè)類的名字就能知道,它是處理ParameterName的Discoverer的(在這里我想再說點(diǎn)題外話,我個(gè)人非常贊同Spring這種全命名的編碼風(fēng)格,看到命名就能看明白這個(gè)類是在干什么,所以說代碼應(yīng)當(dāng)是能“自描述”的)

java怎么通過反射獲得方法真實(shí)參數(shù)名

java怎么通過反射獲得方法真實(shí)參數(shù)名

注釋

好,我們?cè)倩氐酱a中來,繼續(xù)看這個(gè)類:發(fā)現(xiàn)它有一段簡(jiǎn)要的注釋:

java怎么通過反射獲得方法真實(shí)參數(shù)名

大意就是這個(gè)類是針對(duì)使用了JDK8基于-parameters編譯參數(shù)的ParameterNameDiscoverer的實(shí)現(xiàn),這里這個(gè)-parameters參數(shù)是怎么回事咱們先放一邊。

實(shí)現(xiàn)接口類

繼續(xù)向上看StandardReflectionParameterNameDiscoverer所實(shí)現(xiàn)的這個(gè)接口ParameterNameDiscoverer,打開ParameterNameDiscoverer這個(gè)接口,我們用idea的查看子類的功能,能夠看到它一共有包括StandardReflectionParameterNameDiscoverer在內(nèi)的8個(gè)子類

java怎么通過反射獲得方法真實(shí)參數(shù)名

其中有一個(gè)名字里帶“Default”的子類DefaultParameterNameDiscoverer,按照一般套路來說,帶Default的都是默認(rèn)的實(shí)現(xiàn),那么好了我們優(yōu)先看它吧。

java怎么通過反射獲得方法真實(shí)參數(shù)名

打開DefaultParameterNameDiscoverer,我們發(fā)現(xiàn),他做的大體就是通過判斷standardReflectionAvailable這個(gè)值來走向不同分支流程:一個(gè)是走向剛才提到的利用JDK8編譯參數(shù)的StandardReflectionParameterNameDiscoverer另一個(gè)是走向了LocalVariableTableParameterNameDiscoverer

java怎么通過反射獲得方法真實(shí)參數(shù)名

好,現(xiàn)在又出現(xiàn)了熟悉的StandardReflectionParameterNameDiscoverer了,那么我們返回去看吧,一會(huì)再看另一個(gè)分支的LocalVariableTableParameterNameDiscoverer。

我們回到StandardReflectionParameterNameDiscoverer中,再來看剛才那個(gè)-parameters編譯參數(shù),這是個(gè)什么黑科技?既然他是個(gè)編譯參數(shù),那么咱們不妨試著用它編譯一下咱們的代碼試一下吧。

java怎么通過反射獲得方法真實(shí)參數(shù)名

我們將idea設(shè)置上-parameters編譯參數(shù)從新運(yùn)行剛才的demo,發(fā)現(xiàn)這回的輸出結(jié)果是:

java怎么通過反射獲得方法真實(shí)參數(shù)名

已經(jīng)能夠拿到參數(shù)的真實(shí)名稱了。那么,這個(gè)-parameters到底是什么呢:我們可以來看一下oracle官方提供的javac文檔:

java怎么通過反射獲得方法真實(shí)參數(shù)名

java怎么通過反射獲得方法真實(shí)參數(shù)名

通過文檔可以看出加上這個(gè)參數(shù)后,編譯器會(huì)生成元數(shù)據(jù),從而使方法參數(shù)的反射能夠拿到參數(shù)的信息。
這個(gè)功能是jdk8的新特性,我們就不仔細(xì)展開了,詳情可以查看這兩篇文檔:
JDK 8 Features

JEP 118: Access to Parameter Names at Runtime

-parameters這個(gè)黑科技咱們已經(jīng)了解了,利用這個(gè)編譯參數(shù)是可以獲得方法參數(shù)的真實(shí)名稱的,但是這個(gè)參數(shù)是jdk8之后才有的,那么之前的版本如何獲得呢?我們繼續(xù)看Spring源代碼吧?,F(xiàn)在我們來看另一個(gè)分支:LocalVariableTableParameterNameDiscoverer,打開這個(gè)類:

java怎么通過反射獲得方法真實(shí)參數(shù)名

其實(shí)看注釋就明白了,這個(gè)LocalVariableTableParameterNameDiscoverer是通過ASM library分析LocalVariableTable來實(shí)現(xiàn)獲得參數(shù)實(shí)際名稱的,ASM是一個(gè)第三方的字節(jié)碼操縱庫,用這個(gè)庫可以讀取寫入class文件,這個(gè)庫有很廣泛的應(yīng)用,具體的我不展開介紹了。

java怎么通過反射獲得方法真實(shí)參數(shù)名

我們重點(diǎn)說一下這個(gè)LocalVariableTable吧,這個(gè)LocalVariableTable是什么呢?我們不用文字來說明了,直接來看代碼吧:
我們這次不看源文件了,來直接看編譯后的class文件。

編譯后的class文件

idea打開Test1.class

java怎么通過反射獲得方法真實(shí)參數(shù)名

然后在View菜單中點(diǎn)選Show Bytecode

java怎么通過反射獲得方法真實(shí)參數(shù)名

在彈出窗口中,我們可以看到,idea以大綱的方式把class文件的信息列了出來,而在其中就有LocalVariableTable存在,而且在“LocalVariableTable”附近我們可以看到我們定義方法的參數(shù)的真實(shí)名稱。現(xiàn)在我們也就明白了,對(duì)于8以下的jdk編譯環(huán)境,Spring是使用ASM來讀取class文件中LocalVariableTable信息從而獲得參數(shù)真實(shí)名稱的。
到此為止,我們已經(jīng)基本了解了Spring中自動(dòng)綁定背后的黑科技了。

這里我還想繼續(xù)再多說一點(diǎn),有關(guān)LocalVariableTable和Java class文件:class文件可以說是Java實(shí)現(xiàn)跨平臺(tái)特性的根本!不管在什么平臺(tái)下,只要編譯出來的class文件符合規(guī)范,虛擬機(jī)就能夠正常的執(zhí)行。了解一下class文件的相關(guān)知識(shí)其實(shí)對(duì)于理解各類class文件操縱庫以及基于class操縱的AOP等等編程模式的原理是很有幫助的,所以我們可以了解一下class文件是什么樣的結(jié)構(gòu)的。想要了解class文件的結(jié)構(gòu),最權(quán)威的莫過于官方的《Java虛擬機(jī)規(guī)范了》,在Java虛擬機(jī)規(guī)范中,第四章是有關(guān)class文件結(jié)構(gòu)的內(nèi)容,我們可以大致過一遍。
通過閱讀,我們可以大致了解到class的結(jié)構(gòu):

A class file consists of a stream of 8-bit bytes. All 16-bit, 32-bit, and 64-bit
quantities are constructed by reading in two, four, and eight consecutive 8-bit
bytes, respectively. Multibyte data items are always stored in big-endian order,
where the high bytes come first. In the Java SE platform, this format is supported
by interfaces java.io.DataInput and java.io.DataOutput and classes such as
java.io.DataInputStream and java.io.DataOutputStream.

class文件結(jié)構(gòu)

class文件可以用一個(gè)結(jié)構(gòu)來表示:

java怎么通過反射獲得方法真實(shí)參數(shù)名

這個(gè)結(jié)構(gòu)中每一項(xiàng)大致的含義我們來簡(jiǎn)單說明一下吧(詳情請(qǐng)查看虛擬機(jī)規(guī)范):

開頭的magic u4叫做“魔數(shù)”,Java虛擬器通過讀取這個(gè)數(shù)來判斷當(dāng)前文件是不是有效的u4代表它是無符號(hào)4個(gè)byte,這個(gè)數(shù)始終應(yīng)該是0xCAFEBABE;

minor_version、major_version分別是class文件的次版本主版本;

u2 constant_pool_count 、cp_info constant_pool[constant_pool_count-1]代表常量池中項(xiàng)目數(shù)和代表了常量池本身;

u2 access_flags : 代表class訪問標(biāo)記,例如:public protected;

u2 this_class : 代表放置類名在常量池中的索引;

u2 super_class : 代表父類名稱在常量池中的索引;

u2 interfaces_countu2 interfaces[interfaces_count]; 代表所實(shí)現(xiàn)的接口集合的大小,及接口集合本身;

u2 fields_countfield_info fields[fields_count]; 代表屬性集合大小以及屬性集合本身;

u2 methods_countmethod_info methods[methods_count]; 代表方法集合大小以及方法集合本身;

u2 attributes_countattribute_info attributes[attributes_count]; java class文件內(nèi)部屬性信息集合大小和內(nèi)部屬性信息集合本身。這里提一下,我們前面的提到的LocalVariableTable的信息就存儲(chǔ)在這里。

以上就是“java怎么通過反射獲得方法真實(shí)參數(shù)名”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI