聊聊在獲取方法參數名方面,Spring真的就比Mybatis強?
前言
在使用 Spring MVC 寫Controller的時候,即使不使用注解,只要參數名和請求參數的key對應上了,就能自動完成數值的封裝。
但是在使用 Mybatis框架寫接口方法向xml里的SQL語句傳參時,必須使用@Param('')指定key值,在SQL中才可以取到。
Spring可以做到,難道Mybatis做不到嗎?難道Mybatis技術不行?
一、Spring是如何獲取方法參數名稱的?
Spring框架自己寫了一個工具類,用來專門獲取方法參數名稱,DefaultParameterNameDiscoverer類是一個聚合類,維護了一個LinkedList集合,里面的是真正處理的業(yè)務的類對象。
真正處理獲取方法參數名稱有三種情況:
- 一種是處理Kotlin的情況(KotlinReflectionParameterNameDiscoverer)
- 一種是通過java的反射方式獲?。⊿tandardReflectionParameterNameDiscoverer)
- 最后是通過ASM字節(jié)碼方式從LocalVariableTable中獲取(LocalVariableTableParameterNameDiscoverer)
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer { 默認添加幾個參數名稱獲取的工具 public DefaultParameterNameDiscoverer() { if (KotlinDetector.isKotlinReflectPresent() && !GraalDetector.inImageCode()) { addDiscoverer(new KotlinReflectionParameterNameDiscoverer()); } /// add 會添加到一個 LinkedList 集合中,是一個有序的集合 /// 所以這里也就是按照優(yōu)先級進行添加的 StandardReflectionParameterNameDiscoverer 這個類要求JDK1.8以上的版本,且編譯要加上 -parameters 參數 /// 其實就是調用了 method.getParameters() 方法 addDiscoverer(new StandardReflectionParameterNameDiscoverer()); /// LocalVariableTableParameterNameDiscoverer 沒有jdk版本要求, // 是通過ASM提供的通過字節(jié)碼獲取方法的參數名稱 // 但是依賴 javac 編譯的時候 添加上 -g 參數 addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); } }
Spring獲取參數名稱的兩種方式
1、StandardReflectionParameterNameDiscoverer
這種是通過java的反射來后去參數名稱的。但是通過反射獲取方法參數名稱是有前置條件的:
1、要求jdk8以上
2、編譯的時候,必須加-parameters參數,例如:javac -parameters xxxx.java
只有滿足以上條件編譯出來的.class,才能通過反射獲取到方法參數的名稱。
2、LocalVariableTableParameterNameDiscoverer
這種是通過ASM框架,解析字節(jié)碼文件來得到方法參數名稱的。同樣,此種方法也有前置條件:
1、編譯的時候,必須添加-g參數,javac -g xxxx.java
兩種方式對比
可以看出來,spring會先采用StandardReflectionParameterNameDiscoverer嘗試去獲取參數名稱
如果獲取不到就嘗試通過LocalVariableTableParameterNameDiscoverer后去參數名稱。
通過java反射獲取參數名稱的方法,前置條件比較嚴格,但是獲取方式比較簡單,直接通過反射的api調用就行,不依賴其他三方框架。
而ASM解析字節(jié)碼方式的前置條件相對比較寬松,只需要編譯的時候添加 -g參數就行,缺點就是依賴于ASM框架。(Spring已經把asm框架通過源碼的形式加入到spring框架中了,所以不需要單獨在去引用asm框架)
通常,我們
二、Mybatis為什么沒有向Spring學習?
Mybatis要獲取的是接口方法的參數名稱
Mybatis團隊難道不知道ASM技術嗎?這個肯定不是。
其實真正原因在于,Mybatis框架需要獲取的是接口的方法參數名稱,而Spring需要獲取的是類的方法的參數名稱。這是類方法和接口方法是完全不一樣的兩個東西。
Java 要獲取接口或者抽象方法的參數的名稱,必須的是JDK8以上,而且編譯的時候加上-parameters參數,只有這種情況下編譯出來的.class才能獲取到參數的名稱。無論你是通過java反射還是asm字節(jié)碼技術,前面兩個條件必須同時滿足。
所以當達到上述兩個條件后,直接通過java的反射就可以直接獲取參數名稱,根本沒必要通過asm技術去獲取。
所以!Mybatis不是拿不到參數名稱,而是必須要 jdk8 以上而且還得是-parameters編譯才可以,當滿足這兩個條件的時候,你也可以不加@Param('')注解。
三、總結
最后總結一下:
SpringMVC也不是什么時候都可以在不借助注解的情況下獲取到參數名稱,完成自動綁定的,只不過是要到達的前置條件比較寬松,需要編譯的時候添加-g參數,而-g這個參數一般編譯的時候都會加上,因為有了這個參數,在生成的class文件中,添加具體的 line,source,vars 信息。在輸出日志的時候,才能看到行號等信息,如果發(fā)生錯誤了,就很容易定位。一般在idea這種開發(fā)工具中運行代碼的時候,都是默認有這個參數的。所以你再idea寫測試代碼的時候,通過asm總是能獲取到方法的參數名稱,及時沒有設置-g參數,那是因為開發(fā)工具編譯的時候,就默認給你添加了。
不信的話,你可以手動編譯一個java文件,只通過javac 不添加任何參數,然后運行,asm框架也獲取不到方法參數名稱。
maven打包好像是默認都會加-g參數,所以你碰到的絕大多數情況,下通過asm都是可以獲取到參數名稱的,給人一種asm一定能獲取參數名稱的錯覺。
所以在使用Spring框架的時候,一般不用加注解,通常情況下框架也是可以拿到參數名稱的。
Mybatis,一般情況下都要加注解@Param,是因為,Mybatis需要獲取的是接口的方法參數名稱,要想拿到接口方法的參數名稱的話,就必須在jdk8以上的環(huán)境下,而且編譯必須加-parameters參數的時候才能獲取到,這種情況一般生產環(huán)境下不會有-parameters參數,所以在寫Mybatis的接口的時候最好加上@Param參數,以保證程序正常運行。
四、深入拓展
1、從字節(jié)碼說起
java源文件經過java編譯器編譯為.class字節(jié)碼文件,之后才能被JVM執(zhí)行,所以我們可以獲取到的信息都存在.class文件中,但是編譯器編譯.class的的文件默認不保留方法參數名。所以如果只是通過javac命令不添加任何參數情況下,編譯出來的.class文件,是無論如何也不能獲取到方法的參數名稱的。
2、看看普通類在不同參數編譯下的.class字節(jié)碼里面都有什么
下面我們通過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反射都是無法獲取方法參數名稱的,因為字節(jié)碼里面就沒有參數名的信息。
但是如果是有-g參數編譯的話,字節(jié)碼里會在LocalVariableTable將方法的參數名稱進行保存,而ASM框架是基于字節(jié)碼技術的,所以是可以解析出參數名稱的。但是java的反射API并未提供獲取LocalVariableTable信息的內容,所以-g產生的信息,只能通過自己解析字節(jié)碼獲取,所以asm是可以做到的。
如果是有-parameters參數編譯的話,字節(jié)碼里面保存MethodParameters信息,里面就是方法參數名稱的信息,java的反射api提供的接口可以直接獲取到里面的信息。但是-parameters 是jdk8以后才提供的新特性,所以要想通過反射api獲取的話,只能是jdk8及以上版本通過添加-parameters參數才可以。
3、看看接口在不同參數編譯下的.class字節(jié)碼里面都有什么
先寫一個普通的接口
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字節(jié)碼文件才帶有 接口方法參數名的信息,-g 編譯出來的和默認的都是沒有的。
所以嘛,對于接口而言,只有通過-parameters 編譯的字節(jié)碼才能有方法參數名稱。這也就明白了為什么Mybatis的接口要加注解了吧,因為你如果不通過注解信息去獲取的話,你不論是java反射也好,asm也好都拿不到參數名稱,除非你編譯的時候添加-parameters,而生產環(huán)境通常是不會加這個參數的。
五、結束
看到這里應該弄清楚,獲取參數名稱的套路了吧!
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot結合SpringSecurity實現圖形驗證碼功能
這篇文章主要介紹了SpringBoot + SpringSecurity 實現圖形驗證碼功能,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05mybatis-plus的自動填充時間的問題(添加到數據庫的時間比當前時間多4個小時)
這篇文章主要介紹了mybatis-plus的自動填充時間的問題(添加到數據庫的時間比當前時間多4個小時),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09springboot編程式事務TransactionTemplate的使用說明
這篇文章主要介紹了springboot編程式事務TransactionTemplate的使用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06