聊聊在獲取方法參數名方面,Spring真的就比Mybatis強?
前言
在使用 Spring MVC 寫Controller的時候,即使不使用註解,隻要參數名和請求參數的key對應上瞭,就能自動完成數值的封裝。
但是在使用 Mybatis框架寫接口方法向xml裡的SQL語句傳參時,必須使用@Param(”)指定key值,在SQL中才可以取到。
Spring可以做到,難道Mybatis做不到嗎?難道Mybatis技術不行?
一、Spring是如何獲取方法參數名稱的?
Spring框架自己寫瞭一個工具類,用來專門獲取方法參數名稱,DefaultParameterNameDiscoverer類是一個聚合類,維護瞭一個LinkedList集合,裡面的是真正處理的業務的類對象。
真正處理獲取方法參數名稱有三種情況:
- 一種是處理Kotlin的情況(KotlinReflectionParameterNameDiscoverer)
- 一種是通過java的反射方式獲取(StandardReflectionParameterNameDiscoverer)
- 最後是通過ASM字節碼方式從LocalVariableTable中獲取(LocalVariableTableParameterNameDiscoverer)
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { 默認添加幾個參數名稱獲取的工具 public DefaultParameterNameDiscoverer() { if (KotlinDetector.isKotlinReflectPresent() && !GraalDetector.inImageCode()) { addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); } /// add 會添加到一個 LinkedList 集合中,是一個有序的集合 /// 所以這裡也就是按照優先級進行添加的 StandardReflectionParameterNameDiscoverer 這個類要求JDK1.8以上的版本,且編譯要加上 -parameters 參數 /// 其實就是調用瞭 method.getParameters() 方法 addDiscoverer(new StandardReflectionParameterNameDiscoverer()); /// LocalVariableTableParameterNameDiscoverer 沒有jdk版本要求, // 是通過ASM提供的通過字節碼獲取方法的參數名稱 // 但是依賴 javac 編譯的時候 添加上 -g 參數 addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); } }
Spring獲取參數名稱的兩種方式
1、StandardReflectionParameterNameDiscoverer
這種是通過java的反射來後去參數名稱的。但是通過反射獲取方法參數名稱是有前置條件的:
1、要求jdk8以上
2、編譯的時候,必須加-parameters參數,例如:javac -parameters xxxx.java
隻有滿足以上條件編譯出來的.class,才能通過反射獲取到方法參數的名稱。
2、LocalVariableTableParameterNameDiscoverer
這種是通過ASM框架,解析字節碼文件來得到方法參數名稱的。同樣,此種方法也有前置條件:
1、編譯的時候,必須添加-g參數,javac -g xxxx.java
兩種方式對比
可以看出來,spring會先采用StandardReflectionParameterNameDiscoverer嘗試去獲取參數名稱
如果獲取不到就嘗試通過LocalVariableTableParameterNameDiscoverer後去參數名稱。
通過java反射獲取參數名稱的方法,前置條件比較嚴格,但是獲取方式比較簡單,直接通過反射的api調用就行,不依賴其他三方框架。
而ASM解析字節碼方式的前置條件相對比較寬松,隻需要編譯的時候添加 -g參數就行,缺點就是依賴於ASM框架。(Spring已經把asm框架通過源碼的形式加入到spring框架中瞭,所以不需要單獨在去引用asm框架)
通常,我們
二、Mybatis為什麼沒有向Spring學習?
Mybatis要獲取的是接口方法的參數名稱
Mybatis團隊難道不知道ASM技術嗎?這個肯定不是。
其實真正原因在於,Mybatis框架需要獲取的是接口的方法參數名稱,而Spring需要獲取的是類的方法的參數名稱。這是類方法和接口方法是完全不一樣的兩個東西。
Java 要獲取接口或者抽象方法的參數的名稱,必須的是JDK8以上,而且編譯的時候加上-parameters參數,隻有這種情況下編譯出來的.class才能獲取到參數的名稱。無論你是通過java反射還是asm字節碼技術,前面兩個條件必須同時滿足。
所以當達到上述兩個條件後,直接通過java的反射就可以直接獲取參數名稱,根本沒必要通過asm技術去獲取。
所以!Mybatis不是拿不到參數名稱,而是必須要 jdk8 以上而且還得是-parameters編譯才可以,當滿足這兩個條件的時候,你也可以不加@Param(”)註解。
三、總結
最後總結一下:
SpringMVC也不是什麼時候都可以在不借助註解的情況下獲取到參數名稱,完成自動綁定的,隻不過是要到達的前置條件比較寬松,需要編譯的時候添加-g參數,而-g這個參數一般編譯的時候都會加上,因為有瞭這個參數,在生成的class文件中,添加具體的 line,source,vars 信息。在輸出日志的時候,才能看到行號等信息,如果發生錯誤瞭,就很容易定位。一般在idea這種開發工具中運行代碼的時候,都是默認有這個參數的。所以你再idea寫測試代碼的時候,通過asm總是能獲取到方法的參數名稱,及時沒有設置-g參數,那是因為開發工具編譯的時候,就默認給你添加瞭。
不信的話,你可以手動編譯一個java文件,隻通過javac 不添加任何參數,然後運行,asm框架也獲取不到方法參數名稱。
maven打包好像是默認都會加-g參數,所以你碰到的絕大多數情況,下通過asm都是可以獲取到參數名稱的,給人一種asm一定能獲取參數名稱的錯覺。
所以在使用Spring框架的時候,一般不用加註解,通常情況下框架也是可以拿到參數名稱的。
Mybatis,一般情況下都要加註解@Param,是因為,Mybatis需要獲取的是接口的方法參數名稱,要想拿到接口方法的參數名稱的話,就必須在jdk8以上的環境下,而且編譯必須加-parameters參數的時候才能獲取到,這種情況一般生產環境下不會有-parameters參數,所以在寫Mybatis的接口的時候最好加上@Param參數,以保證程序正常運行。
四、深入拓展
1、從字節碼說起
java源文件經過java編譯器編譯為.class字節碼文件,之後才能被JVM執行,所以我們可以獲取到的信息都存在.class文件中,但是編譯器編譯.class的的文件默認不保留方法參數名。所以如果隻是通過javac命令不添加任何參數情況下,編譯出來的.class文件,是無論如何也不能獲取到方法的參數名稱的。
2、看看普通類在不同參數編譯下的.class字節碼裡面都有什麼
下面我們通過javac編譯一個類,然後通過javap 看看裡面的內容都有什麼!
public class TestBean { public String myMethod(String helloWord){ return ""; } }
我們分別用三種方式進行編譯,然後通過javap -verbose TestBean.class 進行查看
javac TestBean.java javac -g TestBean.java javac -parameters TestBean.java
從上面截圖可以看出來:
添加-g參數後,.class會多出LocalVariableTable的信息,裡面可以看到有方法參數的名字“helloWord”。
添加-parameters參數後,.class會多出MethodParameters的信息,裡面也可以看到有方法參數的名字“helloWord”。
而javac不加任何參數的話,則在任何地方都找不到方法參數名“helloword”的信息。
所以:也驗證瞭前面所說的,默認情況下,無論是asm還是java反射都是無法獲取方法參數名稱的,因為字節碼裡面就沒有參數名的信息。
但是如果是有-g參數編譯的話,字節碼裡會在LocalVariableTable將方法的參數名稱進行保存,而ASM框架是基於字節碼技術的,所以是可以解析出參數名稱的。但是java的反射API並未提供獲取LocalVariableTable信息的內容,所以-g產生的信息,隻能通過自己解析字節碼獲取,所以asm是可以做到的。
如果是有-parameters參數編譯的話,字節碼裡面保存MethodParameters信息,裡面就是方法參數名稱的信息,java的反射api提供的接口可以直接獲取到裡面的信息。但是-parameters 是jdk8以後才提供的新特性,所以要想通過反射api獲取的話,隻能是jdk8及以上版本通過添加-parameters參數才可以。
3、看看接口在不同參數編譯下的.class字節碼裡面都有什麼
先寫一個普通的接口
public interface TestInterfaceBean { public String testInterfaceMethod (String iName); }
然後再用三種方式進行編譯,最後通過javap -verbose TestInterfaceBean.class 進行查看
javac TestInterfaceBean.java javac -g TestInterfaceBean.java javac -parameters TestInterfaceBean.java
從上面三張截圖可以看出來,隻有 javac -parameters 編譯出來的.class字節碼文件才帶有 接口方法參數名的信息,-g 編譯出來的和默認的都是沒有的。
所以嘛,對於接口而言,隻有通過-parameters 編譯的字節碼才能有方法參數名稱。這也就明白瞭為什麼Mybatis的接口要加註解瞭吧,因為你如果不通過註解信息去獲取的話,你不論是java反射也好,asm也好都拿不到參數名稱,除非你編譯的時候添加-parameters,而生產環境通常是不會加這個參數的。
五、結束
看到這裡應該弄清楚,獲取參數名稱的套路瞭吧!
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 論java如何通過反射獲得方法真實參數名及擴展研究
- Spring配置文件中parent與abstract的使用
- Javac/javap 自帶工具簡單使用講解
- Mybatis省略@Param註解原理分析
- Mybatis Mapper中多參數方法不使用@param註解報錯的解決