聊聊在獲取方法參數名方面,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。

推薦閱讀: