[附一]: spring 為什么可以獲取參數(shù)名而mybatis不可以

1. 為什么可以獲得函數(shù)的參數(shù)名?

一般 javac 編譯出的 .class 方法是不包含參數(shù)名的, 但 .class 文件除了反編譯出的類, 還包含額外的信息, 其中對(duì)日常最有用的2個(gè)概念是:

  • LineNumberTable
    記錄字節(jié)碼偏移量和代碼行號(hào)的 (拋異常時(shí), 顯示的哪行代碼出錯(cuò), 就是用的 LineNumberTable 的信息)
  • LocalVariableTable (本地變量表)
    記錄了方法中的局部變量和源代碼中的局部變量的對(duì)應(yīng)關(guān)系 (獲取函數(shù)的參數(shù)名等)

ASM 可以讀取 .class 文件中的 LocalVariableTable

2. 獲得參數(shù)名的3個(gè)方法:

  1. 如果是 java8, 在編譯時(shí)加上 -parameters 參數(shù) java.lang.reflect.Parameter 自動(dòng)解析編譯在 .class 文件的 輸出獲取參數(shù)名
    1. 示例
      • 類中使用 Parameter 反射
      public class TestParamName {
          public static void main1(String[] args) throws NoSuchMethodException {
              Method method = TestParamName.class.getMethod("test1", String.class, Integer.class);
              int parameterCount = method.getParameterCount();
              Parameter[] parameters = method.getParameters();
      
              // 打印輸出:
              System.out.println("方法參數(shù)總數(shù):" + parameterCount);
              Arrays.stream(parameters).forEach(
                      p -> System.out.println(p.getType() + "----" + p.getName())
              );
      
          }
      
          public void test1(String name, Integer age) {
              System.out.println(name + "::" + age);
          }
      }
      
      • javac 上加上 -parameters 參數(shù)編譯額外信息
      javac -parameters TestParamName.java
      
      • 輸出:
      // 編譯時(shí)加上 -parameters 參數(shù), 輸出: 
      方法參數(shù)總數(shù):2
      class java.lang.String----name
      class java.lang.Integer----age
      
      // 編譯時(shí)不加 -parameters 參數(shù), 輸出: 
      方法參數(shù)總數(shù):2
      class java.lang.String----arg0
      class java.lang.Integer----arg1
      
    2. 缺點(diǎn)
      • 這種方式強(qiáng)制要求編譯時(shí)增加參數(shù), 且 java 版本在 1.8以上. 編譯參數(shù)往往每個(gè)人不一樣. 解決辦法是通過 maven 插件打包. (但是這容易和 idea 開發(fā)時(shí)輸出不一致, idea 的 java compiler 選項(xiàng)可能沒加 -parameters 參數(shù))
      <!-- 編譯環(huán)境在1.8編譯 且附加編譯參數(shù):-parameters-->
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
          <configuration>
              <!-- 編譯參數(shù) -->
              <compilerArgs>
                  <arg>-parameters</arg>
              </compilerArgs>
              <source>${java.version}</source>
              <target>${java.version}</target>
              <compilerVersion>${java.version}</compilerVersion>
              <encoding>${project.build.sourceEncoding}</encoding>
          </configuration>
      </plugin>
      
  2. 如果是 java8 以下, 添加 -g 參數(shù), 但因?yàn)闆]有 java.lang.reflect.Parameter 類, 需要自己使用 javap -verbose 將局部變量日志輸出, 然后進(jìn)行解析.
    • 缺點(diǎn): 代碼里需要解析 javap 的輸出
  1. 使用 ASM 工具 (推薦)
    ASM 是一個(gè)字節(jié)碼操控框架, 它能夠動(dòng)態(tài)增強(qiáng)類功能, 改變類型為, 甚至生成新類

3. Spring mvc 為何可以推斷出參數(shù)名, 而 Mybatis 推斷不出來(lái)?

  1. 有一個(gè)很疑惑的現(xiàn)象是:

    • 在使用Spring MVC的時(shí)候, 即便不加 @Param 注解, 也可以將 json 的 key 和 controller 方法的參數(shù)綁定
    • 在使用MyBatis(接口模式)時(shí),接口方法向 sql 語(yǔ)句傳參時(shí),必須使用@Param('')指定函數(shù)參數(shù)和 sql 參數(shù)的綁定關(guān)系
  2. 幾種操作字節(jié)碼方式的區(qū)別

    • ASM: 操縱底層的 JVM 匯編指令. 要求使用者對(duì)class組織結(jié)構(gòu)和JVM匯編指令有一定的了解
    • java 動(dòng)態(tài)代理:
      動(dòng)態(tài)代理是調(diào)用 Proxy的newProxyInstance()方法 創(chuàng)建一個(gè)新的代理類 $Proxy0, 內(nèi)部是一系列反射 constructor . 該代理類繼承自 java.lang.reflect.Proxy, 且 implement 了需要代理的接口. $Proxy0 extends Proxy implements MyInterface{ ... }.
      java 的單繼承機(jī)制決定了不能對(duì)抽象類代理, 只能對(duì)接口代理
    • CGLIB 動(dòng)態(tài)代理: 使用 ASM 框架操縱類實(shí)現(xiàn)對(duì)類(或接口)的代理

3.為什么 spring mvc 可以自定映射 json key 和方法參數(shù)名?
Spring MVC 借助 ASM 來(lái)實(shí)現(xiàn)參數(shù)名讀取, 不依賴于 -parameters 參數(shù),也不依賴于 -g 編譯參數(shù). 最終依賴 spring core 的 DefaultParameterNameDiscoverer 類獲取參數(shù)名

```java
public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
    public DefaultParameterNameDiscoverer() {
        if (KotlinDetector.isKotlinReflectPresent() && !GraalDetector.inImageCode()) {
            addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
        }
        addDiscoverer(new StandardReflectionParameterNameDiscoverer());
        addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
    }

    // 只要有一個(gè)處理器獲取了參數(shù)名, 就返回
    public String[] getParameterNames(Method method) {
        for (ParameterNameDiscoverer pnd : this.parameterNameDiscoverers) {
            String[] result = pnd.getParameterNames(method);
            if (result != null) {
                return result;
            }
        }
        return null;
    }
}
```   

DefaultParameterNameDiscoverer 使用職責(zé)鏈模式處理參數(shù)名獲取. 職責(zé)鏈中包括 StandardReflectionParameterNameDiscovererLocalVariableTableParameterNameDiscoverer. 前者依賴 -parameters 的編譯輸出; 后者使用 ASM 實(shí)現(xiàn)參數(shù)名解析

  1. 為什么 Mybatis 的 dao 無(wú)法自動(dòng)完成方法參數(shù)名和 sql 參數(shù)名的映射
    1. 首先, ASM 有個(gè)問題是, 無(wú)法獲得 interface 的方法的參數(shù)名.
      因?yàn)閷?duì)于抽象方法沒有方法體, 也就不存在方法內(nèi)的局部變量, 也就沒有記錄對(duì)應(yīng)的 LocalVariableTable . 所以 ASM 無(wú)法讀取
    2. Mybatis 是通過接口和 sql 綁定后生成代理類完成的查詢映射, 所以使用 ASM 也無(wú)法獲取參數(shù)名
    Mapper
    @Repository // 這個(gè)注解可以不加, 純粹是為了 idea 在 autowired 中不報(bào)錯(cuò)
    public interface CasbinDao {
        // todo  測(cè)試自動(dòng)生成 id
        @Insert("INSERT INTO casbin_rule(id, p_type, v0, v1) VALUES(#{id}, #{pType}, #{v0}, #{v1})")
        @Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
        int insert(CasbinRule casbin);
    
        @Update(value = {
                "<script>",
                "UPDATE users",
                "<set>",
                "<if test='username != null'>, username = #{username}</if>",
                "<if test='password != null'>, password = #{password}</if>",
                "</set>",
                "WHERE id = #{id}",
                "</script>"
        })
        int updateById(CasbinRule user);
    }
    
    但是, 如果在編譯時(shí)加上 -parameters 參數(shù), 是可以使用 java8 的反射獲取參數(shù)名的. 因?yàn)樗灰蕾?LocalVariableTable. 單位了兼容性, mybatis 放棄了這種做法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容