成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

【深度好文】深度分析如何獲取方法參數(shù)名

vslam / 2047人閱讀

摘要:但是這種方式對(duì)于接口和抽象方法是不管用的,因?yàn)槌橄蠓椒](méi)有方法體,也就沒(méi)有局部變量,自然也就沒(méi)有局部變量表了是通過(guò)接口跟語(yǔ)句綁定然后生成代理類(lèi)來(lái)實(shí)現(xiàn)的,因此它無(wú)法通過(guò)解析字節(jié)碼來(lái)獲取方法參數(shù)名。

聲明:本文屬原創(chuàng)文章,首發(fā)于公號(hào):程序員自學(xué)之道,轉(zhuǎn)載請(qǐng)注明出處!
發(fā)現(xiàn)問(wèn)題

對(duì)Java字節(jié)碼有一定了解的朋友應(yīng)該知道,Java 在編譯的時(shí)候,默認(rèn)會(huì)將方法參數(shù)名丟棄,因此我們無(wú)法在運(yùn)行時(shí)獲取參數(shù)名稱(chēng)。但是在使用 SpringMVC 的時(shí)候,我發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,當(dāng)我們需要接收請(qǐng)求參數(shù)的時(shí)候,相應(yīng)的 Controller 方法只需要正常聲明,就可以直接接收正確的參數(shù),例如:

注:以下例子使用 maven 進(jìn)行編譯,且非 SpringBoot 項(xiàng)目,SpringBoot 已經(jīng)自動(dòng)解決了參數(shù)名解析的問(wèn)題,后面咱們會(huì)討論
@RestController
@RequestMapping("calculator")
public class CalculatorController {
    @GetMapping("add")
    public int add(int aNum, int bNum) {
        return aNum + bNum;
    }
}

當(dāng)接收到 http://localhost:8080/calculator/add?aNum=12&bNum=3 這樣的請(qǐng)求時(shí),會(huì)返回 15,即aNum 和 bNum 都能被正確解析。

然而,當(dāng)我們使用 MyBatis 時(shí),如果接口方法有多個(gè)參數(shù)而且我們沒(méi)有打上 @Param 注解的話(huà),執(zhí)行的時(shí)候就會(huì)報(bào)錯(cuò)。例如,我們有如下的接口:

@Mapper
public interface AccountMapper {
    @Select("select * from `account` where `name` = #{name} and mobile_phone = #{mobilePhone}")
    Account getByNameAndMobilePhone(String name, String mobilePhone);
}

方法中包含兩個(gè)參數(shù),但是沒(méi)有打上 @Param 注解,這時(shí)候如果調(diào)用這個(gè)方法,會(huì)報(bào)錯(cuò):

org.apache.ibatis.binding.BindingException: Parameter "name" not found. Available parameters are [arg1, arg0, param1, param2]

從錯(cuò)誤信息中可以看出,是因?yàn)?MyBatis 沒(méi)有正確解析方法參數(shù)名稱(chēng)導(dǎo)致異常

這就很奇怪了,為什么 Spring 可以正確解析方法參數(shù)名稱(chēng),但是 MyBatis 卻不行?Java編譯的時(shí)候不是默認(rèn)會(huì)將方法參數(shù)名丟棄嗎?我只是普通編譯,并沒(méi)有做特殊處理,那Spring又是從哪里找到方法參數(shù)名的呢?

帶著這些問(wèn)題,我開(kāi)始進(jìn)行研究和探索。

獲取參數(shù)名的幾種方式

通過(guò)查閱各種資料,我知道,獲取參數(shù)名稱(chēng)的方式主要有兩種。

一、-g 參數(shù)

當(dāng)我們對(duì) Java 源碼進(jìn)行編譯時(shí),無(wú)論是直接使用命令行還是使用 IDE 為我們編譯,實(shí)際上最終都是調(diào)用 javac 命令進(jìn)行的,在編譯的時(shí)候,我們?nèi)绻砑由?-g 參數(shù),即告訴編譯器,我們需要調(diào)試信息,這時(shí),生成的字節(jié)碼當(dāng)中就會(huì)包含局部變量表的信息(方法參數(shù)也是局部變量),于是我們就可以通過(guò)解析字節(jié)碼獲取參數(shù)名了。

我們用最最經(jīng)典的 HelloWorld 程序中的 main 方法為例,看一下編譯的效果:

 public class HelloWorld{
    public static void main(String[] argsName){
        System.out.println("HelloWorld!");
    }
}

我們直接執(zhí)行如下 javac 命令來(lái)編譯并查看生成的字節(jié)碼信息:

javac HelloWorld.java
javap -verbose HelloWorld.class


可以看到,我們的參數(shù)名 argsName 已經(jīng)被抹掉了。而如果字節(jié)碼中都沒(méi)有我們所需要的信息,那么在運(yùn)行時(shí),反射或者是別的方法也都無(wú)能為力了,巧婦難為無(wú)米之炊吶。

接下來(lái),我們?cè)囈幌绿砑?-g 參數(shù)會(huì)發(fā)生什么:

javac -g HelloWorld.java
javap -verbose HelloWorld.class


可以看到,這里多了一個(gè) LocalVariableTable,即局部變量表,其中就有我們的參數(shù)名稱(chēng) argsName!

那么,我們如何在方法運(yùn)行時(shí)從字節(jié)碼信息中獲取參數(shù)名稱(chēng)呢?你可以直接通過(guò) javap 來(lái)獲取字節(jié)碼信息,然后自己去根據(jù)信息的格式去解析,然而這樣太低效了,而且太繁瑣了。

這時(shí)候如果我們請(qǐng)大名鼎鼎的 ASM 來(lái)當(dāng)“導(dǎo)游”,帶著我們游覽字節(jié)碼內(nèi)部構(gòu)造,實(shí)現(xiàn)起來(lái)就輕松多了。

這個(gè) ASM 可牛了,它不僅可以查看字節(jié)碼的信息,甚至可以動(dòng)態(tài)修改類(lèi)的定義或者新建一個(gè)原本沒(méi)有的類(lèi)!在各種框架中被廣泛地使用,SpringAOP中使用的 CGLib 底層就是使用 ASM 來(lái)實(shí)現(xiàn)的。有興趣可以查看官網(wǎng):https://asm.ow2.io/  之前我也寫(xiě)過(guò)一篇文章《Java用ASM寫(xiě)一個(gè)HelloWorld程序》,有興趣可以看一下。

言歸正傳,如何通過(guò) ASM 來(lái)獲取參數(shù)名稱(chēng)呢? 直接上代碼:

首先添加依賴(lài):


     asm
    asm
    3.3.1
/**
 * 使用字節(jié)碼工具ASM來(lái)獲取方法的參數(shù)名
 */
public static String[] getMethodParamNames(final Method method) throws IOException {
  final int methodParameterCount =  method.getParameterTypes().length;
  final String[] methodParametersNames = new String[methodParameterCount];
  ClassReader cr = new ClassReader(method.getDeclaringClass().getName());
  ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  cr.accept(new ClassAdapter(cw) {
      @Override
      public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
          MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
          final Type[] argTypes = Type.getArgumentTypes(desc);
          //參數(shù)類(lèi)型不一致
          if (!method.getName().equals(name) || !matchTypes(argTypes,  method.getParameterTypes())) {
              return mv;
          }
          return new MethodAdapter(mv) {
              @Override
              public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
                  //如果是靜態(tài)方法,第一個(gè)參數(shù)就是方法參數(shù),非靜態(tài)方法,則第一個(gè)參數(shù)是 this, 然后才是方法的參數(shù)
                  int methodParameterIndex = Modifier.isStatic(method.getModifiers()) ? index : index - 1;
                  if (0 <= methodParameterIndex && methodParameterIndex < methodParameterCount) {
                      methodParametersNames[methodParameterIndex] = name;
                  }
                  super.visitLocalVariable(name, desc, signature, start, end, index);
              }
          };
      }
  }, 0);
  return methodParametersNames;
}

/**
 * 比較參數(shù)是否一致
 */
private static boolean matchTypes(Type[] types, Class[] parameterTypes) {
   if (types.length != parameterTypes.length) {
       return false;
   }
   for (int i = 0; i < types.length; i++) {
       if (!Type.getType(parameterTypes[i]).equals(types[i])) {
           return false;
       }
   }
   return true;
}

簡(jiǎn)而言之,ASM使用了訪(fǎng)問(wèn)者模式,它就像一個(gè)導(dǎo)游,帶著我們?nèi)ビ斡[字節(jié)碼文件中的各個(gè)“景點(diǎn)”。我們實(shí)現(xiàn)不同的 Visitor 接口就像是手上握有不同景點(diǎn)門(mén)票的游客,導(dǎo)游會(huì)帶著 ClassVisitor 去總體參觀(guān)類(lèi)定義的景觀(guān),而類(lèi)內(nèi)部有方法,如果你想看一下方法內(nèi)部的定義,需要"額外購(gòu)票",即需要實(shí)現(xiàn) MethodVisitor 才能跟著導(dǎo)游去參觀(guān)方法定義這個(gè)景點(diǎn)。而在游覽各個(gè)景點(diǎn)的時(shí)候,我們可以只游覽我們感興趣的部分,這就可以繼承適配器(ClassAdapter和MethodAdapter分別是ClassVisitor和MethodVisitor的適配器)然后只實(shí)現(xiàn)我們感興趣的方法即可。

這里對(duì)于類(lèi)的定義,我們只對(duì)方法感興趣,因此只實(shí)現(xiàn) visitMethod 方法;在方法中,我們只對(duì) LocalVariableTable 有興趣,因此只實(shí)現(xiàn) visitLocalVariable 方法。這樣我們得到了局部變量表,再根據(jù)一些規(guī)則就可以拿到我們的參數(shù)名稱(chēng)了!是不是很棒!

順便說(shuō)一下,如果你使用 maven 來(lái)管理項(xiàng)目的話(huà),這個(gè) -g 參數(shù)會(huì)在編譯的時(shí)候自動(dòng)加上,因此我們不需要額外添加就可以通過(guò)字節(jié)碼拿到,這也就是為什么 SpringMVC 可以拿到方法參數(shù)名稱(chēng)的原因。

但是這種方式對(duì)于接口和抽象方法是不管用的,因?yàn)槌橄蠓椒](méi)有方法體,也就沒(méi)有局部變量,自然也就沒(méi)有局部變量表了

MyBatis 是通過(guò)接口跟 SQL 語(yǔ)句綁定然后生成代理類(lèi)來(lái)實(shí)現(xiàn)的,因此它無(wú)法通過(guò)解析字節(jié)碼來(lái)獲取方法參數(shù)名。

雖然通過(guò)字節(jié)碼的方法的確可以拿到參數(shù)名,但還是不方便,而且它對(duì)接口和抽象方法的參數(shù)名也無(wú)能為力。有沒(méi)有更方便更全面的方法呢?答案是:有的。

-parameters 參數(shù)

JDK8 在反射包中引入了 java.lang.reflect.Parameter 來(lái)獲取參數(shù)相關(guān)的信息

A small but useful example is support for method parameter names at run time: storing such names in the class file structure goes hand in hand with offering a standard API to retrieve them (java.lang.reflect.Parameter) - 《The Java Virtual Machine Specification》

但是它依賴(lài)于編譯時(shí)添加 -parameters 參數(shù),也就是說(shuō),只有在編譯的時(shí)候添加了這個(gè)參數(shù)才能在運(yùn)行時(shí)通過(guò)反射獲取參數(shù)信息。還是用我們的 HelloWorld 程序,我們來(lái)試一下添加 -parameters 參數(shù):

javac -parameters HelloWorld.java
javap -verbose HelloWorld.class


可以看到,字節(jié)碼文件中多了 MethodParameters 部分,里面存放的就直接是我們所需要在的參數(shù)名!我們可以直接通過(guò)反射獲?。?/strong>

HelloWorld.class.getMethod("main",String[].class).getParameters()[0].getName()

問(wèn)題來(lái)了,我們?nèi)绾卧诰幾g的時(shí)候自動(dòng)加上 -parameters 這個(gè)參數(shù)呢?畢竟我們不可能只在自己的 IDE 上做設(shè)置,也不可能自己寫(xiě)腳本來(lái)編譯。

如果你使用 maven 來(lái)管理項(xiàng)目的話(huà),可以直接通過(guò)插件來(lái)完成:


  
      
          org.apache.maven.plugins
          maven-compiler-plugin
          3.8.0
          
              ${java.version}
              ${java.version}
              true
          
      
  

這樣這個(gè) -parameters 參數(shù)就會(huì)在編譯的時(shí)候自動(dòng)加上了。

關(guān)于 SpringBoot

文章開(kāi)頭曾提到,SpringBoot 已經(jīng)自動(dòng)解決了參數(shù)名解析的問(wèn)題,它其實(shí)就是通過(guò) -parameters 參數(shù)來(lái)實(shí)現(xiàn)的。在 spring-boot-starter-parent.pom 文件中它為我們添加了上面提到的插件及參數(shù):

有了這個(gè)參數(shù)而且是在 JDK8+ 中運(yùn)行的話(huà)無(wú)論是 SpringMVC 還是 MyBatis 都可以獲取到正確的方法參數(shù)名了!

總結(jié)

獲取參數(shù)名稱(chēng)的方式主要有兩種:

編譯時(shí)添加 -g 參數(shù),然后通過(guò)解析字節(jié)碼讀取局部變量表獲取

maven在編譯時(shí)會(huì)自動(dòng)添加這個(gè)參數(shù),但是用的時(shí)候需要解析字節(jié)碼,而且對(duì)于接口和抽象方法無(wú)能為力,因?yàn)榻涌诤统橄蠓椒](méi)有方法體,也就沒(méi)有局部變量,因此也就沒(méi)有局部變量表,所以無(wú)法通過(guò)局部變量表來(lái)獲取參數(shù)名稱(chēng)。

JDK8+ 編譯時(shí)添加 -parameters 參數(shù),然后通過(guò)反射獲取

可以通過(guò)配置插件自動(dòng)添加,使用非常方便,直接通過(guò)反射即可拿到參數(shù)信息。但是需要 JDK8 及以上才能使用。

SpringMVC 和 MyBatis :

有 -parameters 參數(shù)的場(chǎng)景,兩個(gè)框架都可以正確解析參數(shù)名。

只有 -g 參數(shù)時(shí)

SpringMVC 通過(guò)解析字節(jié)碼獲取 Controller 的方法參數(shù)以綁定請(qǐng)求參數(shù)

MyBatis 需要與接口綁定,而 -g 參數(shù)對(duì)接口和抽象類(lèi)無(wú)效,因此不能正確解析參數(shù)名

-g 和 -parameters 都沒(méi)有時(shí),兩者都無(wú)法正確解析參數(shù)名

后記

不知不覺(jué)寫(xiě)了這么多,現(xiàn)在也快凌晨?jī)牲c(diǎn)了。

對(duì)于獲取方法參數(shù)名這個(gè)問(wèn)題的探究最早其實(shí)是來(lái)源于我在寫(xiě) http-api-invoker (github 地址:https://github.com/dadiyang/h... 這個(gè)框架的時(shí)候意識(shí)到的。這個(gè)框架跟MyBatis類(lèi)似,它將接口與 url 進(jìn)行綁定然后生成代理類(lèi)來(lái)發(fā)送 http 請(qǐng)求,我們無(wú)需關(guān)注參數(shù)拼接和序列化、請(qǐng)求發(fā)送和返回值處理的過(guò)程,只需要定義好我們的接口并打上注解即可。

在不斷優(yōu)化和使用的過(guò)程中我發(fā)現(xiàn),每個(gè)接口方法都需要打 @Param 注解太麻煩,而 MyBatis 也同樣有這個(gè)問(wèn)題,然而 SpringMVC 卻可以解決。因此為了更加完善這個(gè)框架,我開(kāi)始一探究竟。做了很多的功課,把整個(gè)來(lái)龍去脈都了解清楚了,但是一直沒(méi)有時(shí)間整理。

現(xiàn)在終于忙里偷閑趁著周末把這篇文章寫(xiě)出來(lái)了,可惜由于最近我讓 http-api-invoker 框架兼容到 JDK6,還沒(méi)有想好怎樣讓它在支持 JDK6 的前提下更好地利用 JDK8+ 的 -parameters 特性。這個(gè)留到以后再做進(jìn)一步的探索吧。

更多原創(chuàng)好文,請(qǐng)關(guān)注程序員自學(xué)之道

參考文獻(xiàn):

asm4-guide

The Java Virtual Machine Specification

spring mvc如何實(shí)現(xiàn)參數(shù)名綁定

使用ASM獲得JAVA類(lèi)方法參數(shù)名

How to compile Spring Boot applications with Java 8 --parameter flag

Java用ASM寫(xiě)一個(gè)HelloWorld程序

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77803.html

相關(guān)文章

  • 數(shù)據(jù)庫(kù)

    摘要:編輯大咖說(shuō)閱讀字?jǐn)?shù)用時(shí)分鐘內(nèi)容摘要對(duì)于真正企業(yè)級(jí)應(yīng)用,需要分布式數(shù)據(jù)庫(kù)具備什么樣的能力相比等分布式數(shù)據(jù)庫(kù),他們條最佳性能優(yōu)化性能優(yōu)化索引與優(yōu)化關(guān)于索引與優(yōu)化的基礎(chǔ)知識(shí)匯總。 mysql 數(shù)據(jù)庫(kù)開(kāi)發(fā)常見(jiàn)問(wèn)題及優(yōu)化 這篇文章從庫(kù)表設(shè)計(jì),慢 SQL 問(wèn)題和誤操作、程序 bug 時(shí)怎么辦這三個(gè)問(wèn)題展開(kāi)。 一個(gè)小時(shí)學(xué)會(huì) MySQL 數(shù)據(jù)庫(kù) 看到了一篇適合新手的 MySQL 入門(mén)教程,希望對(duì)想學(xué) ...

    mengbo 評(píng)論0 收藏0
  • 數(shù)據(jù)庫(kù)

    摘要:編輯大咖說(shuō)閱讀字?jǐn)?shù)用時(shí)分鐘內(nèi)容摘要對(duì)于真正企業(yè)級(jí)應(yīng)用,需要分布式數(shù)據(jù)庫(kù)具備什么樣的能力相比等分布式數(shù)據(jù)庫(kù),他們條最佳性能優(yōu)化性能優(yōu)化索引與優(yōu)化關(guān)于索引與優(yōu)化的基礎(chǔ)知識(shí)匯總。 mysql 數(shù)據(jù)庫(kù)開(kāi)發(fā)常見(jiàn)問(wèn)題及優(yōu)化 這篇文章從庫(kù)表設(shè)計(jì),慢 SQL 問(wèn)題和誤操作、程序 bug 時(shí)怎么辦這三個(gè)問(wèn)題展開(kāi)。 一個(gè)小時(shí)學(xué)會(huì) MySQL 數(shù)據(jù)庫(kù) 看到了一篇適合新手的 MySQL 入門(mén)教程,希望對(duì)想學(xué) ...

    shuibo 評(píng)論0 收藏0
  • AI開(kāi)發(fā)者福音!阿里云推出國(guó)內(nèi)首個(gè)基于英偉達(dá)NGC的GPU優(yōu)化容器

    摘要:阿里云推出國(guó)內(nèi)首個(gè)基于英偉達(dá)的優(yōu)化容器月日,在云棲大會(huì)深圳峰會(huì)上,阿里云宣布與英偉達(dá)云合作,開(kāi)發(fā)者可以在云市場(chǎng)下載云鏡像和運(yùn)行容器,來(lái)使用阿里云上的計(jì)算平臺(tái)。阿里云成為中國(guó)首家與加速的容器合作的云廠(chǎng)商。 摘要: 3月28日,在2018云棲大會(huì)·深圳峰會(huì)上,阿里云宣布與英偉達(dá)GPU 云 合作 (NGC),開(kāi)發(fā)者可以在云市場(chǎng)下載NVIDIA GPU 云鏡像和運(yùn)行NGC 容器,來(lái)使用阿里云上...

    Sunxb 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<