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

資訊專欄INFORMATION COLUMN

Java 運行時獲取方法參數(shù)名

cfanr / 3539人閱讀

摘要:原文如果覺得我的文章對你有用,請隨意贊賞本文整理運行時獲取方法參數(shù)名的兩種方法,的最新的方法和之前的方法。文件中的調(diào)試信息上文介紹了通過新增的反射運行時獲取方法參數(shù)名。

原文:http://nullwy.me/2017/04/java...
如果覺得我的文章對你有用,請隨意贊賞

本文整理 Java 運行時獲取方法參數(shù)名的兩種方法,Java 8 的最新的方法和 Java 8 之前的方法。

Java 8 的新特性

翻閱 Java 8 的新特性,可以看到有這么一條“JEP 118: Access to Parameter Names at Runtime”。這個特性就是為了能運行時獲取參數(shù)名新加的。這個 JEP 只是功能增強的提案,并沒有最終實現(xiàn)的 JDK 相關(guān)的 API 的介紹。查看“Enhancements to the Reflection API” 會看到如下介紹:

Enhancements in Java SE 8
Method Parameter Reflection: You can obtain the names of the formal parameters of any method or constructor with the method java.lang.reflect.Executable.getParameters. However, .class files do not store formal parameter names by default. To store formal parameter names in a particular .class file, and thus enable the Reflection API to retrieve formal parameter names, compile the source file with the -parameters option of the javac compiler.

javac 文檔中關(guān)于 -parameters 的介紹如下 [doc man ]:

-parameters
Stores formal parameter names of constructors and methods in the generated class file so that the method java.lang.reflect.Executable.getParameters from the Reflection API can retrieve them.

現(xiàn)在試驗下這個特性。有如下兩個文件:

package com.test;

public class TestClass {
    public int sum(int num1, int num2) {
        return num1 + num2;
    }
}
package com.test;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class Java8Main {
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = TestClass.class.getDeclaredMethod("sum", int.class, int.class);
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter.getType().getName() + " " + parameter.getName());
        }
    }
}

先試試 javac 不加 -parameters 編譯,結(jié)果如下:

$ javac -d "target/classes" src/main/java/com/test/*.java
$ java -cp "target/classes" com.test.Java8Main
int arg0
int arg1

加上 -parameters 后,運行結(jié)果如下:

$ javac -d "target/classes" -parameters src/main/java/com/test/*.java
$ java -cp "target/classes" com.test.Java8Main
int num1
int num2

可以看到,加上 -parameters 后,正確獲得了參數(shù)名。實際開發(fā)中,很少直接用命令行編譯 Java 代碼,項目一般都會用 maven 管理。在 maven 下,只需修改 pom 文件的 maven-compiler-plugin 插件配置即可,就是加上了 compilerArgs 節(jié)點 [doc ],如下:


    org.apache.maven.plugins
        maven-compiler-plugin
        
        1.8
        1.8
        
            -parameters
        
    
實現(xiàn)原理

“Enhancements in Java SE 8”提到,參數(shù)名信息回存儲在 class 文件中?,F(xiàn)在試試用 javap( doc man)命令反編譯生成的 class 文件。反編譯 class 文件:

$ javap -v -cp "target/classes" com.test.TestClass
Classfile /Users/yulewei/IdeaProjects/hellojava/target/classes/com/test/TestClass.class
  Last modified 2017-5-2; size 305 bytes
  MD5 checksum 24b99fec7f3062f5de1c3ca4270a1d36
  Compiled from "TestClass.java"
public class com.test.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#15         // java/lang/Object."":()V
   #2 = Class              #16            // com/test/TestClass
   #3 = Class              #17            // java/lang/Object
   #4 = Utf8               
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LineNumberTable
   #8 = Utf8               sum
   #9 = Utf8               (II)I
  #10 = Utf8               MethodParameters
  #11 = Utf8               num1
  #12 = Utf8               num2
  #13 = Utf8               SourceFile
  #14 = Utf8               TestClass.java
  #15 = NameAndType        #4:#5          // "":()V
  #16 = Utf8               com/test/TestClass
  #17 = Utf8               java/lang/Object
{
  public com.test.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 3: 0

  public int sum(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 6: 0
    MethodParameters:
      Name                           Flags
      num1
      num2
}
SourceFile: "TestClass.java"

在結(jié)尾的 MethodParameters 屬性就是,實現(xiàn)運行時獲取方法參數(shù)的核心。這個屬性是 Java 8 的 class 文件新加的,具體介紹可以參考官方“Java 虛擬機官方”文檔的介紹,“4.7.24. The MethodParameters Attribute”,doc。

class 文件中的調(diào)試信息

上文介紹了 Java 8 通過新增的反射 API 運行時獲取方法參數(shù)名。那么在 Java 8 之前,有沒有辦法呢?或者在編譯時沒有開啟 -parameters 參數(shù),又如何動態(tài)獲取方法參數(shù)名呢?其實 class 文件中保存的調(diào)試信息就可以包含方法參數(shù)名。

javac-g 選項可以在 class 文件中生成調(diào)試信息,官方文檔介紹如下 [doc man ]:

-g
Generates all debugging information, including local variables. By default, only line number and source file information is generated.
-g:none
Does not generate any debugging information.
-g:[keyword list]
Generates only some kinds of debugging information, specified by a comma separated list of keywords. Valid keywords are:
?? source
???? Source file debugging information.
?? lines
???? Line number debugging information.
?? vars
???? Local variable debugging information.

可以看到默認是包含源代碼信息和行號信息的?,F(xiàn)在試驗下不生成調(diào)試信息的情況:

$ javac -d "target/classes" src/main/java/com/test/*.java -g:none
$ javap -v -cp "target/classes" com.test.TestClass
Classfile /Users/yulewei/IdeaProjects/hellojava/target/classes/com/test/TestClass.class
  Last modified 2017-5-2; size 177 bytes
  MD5 checksum 559f5448154e4d7dd089f8155d8d0f55
public class com.test.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#9          // java/lang/Object."":()V
   #2 = Class              #10            // com/test/TestClass
   #3 = Class              #11            // java/lang/Object
   #4 = Utf8               
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               sum
   #8 = Utf8               (II)I
   #9 = NameAndType        #4:#5          // "":()V
  #10 = Utf8               com/test/TestClass
  #11 = Utf8               java/lang/Object
{
  public com.test.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return

  public int sum(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
}

對比上文的反編譯結(jié)果,可以看到,輸出結(jié)果中的 Compiled from "TestClass.java" 沒了,Constant pool 中也不再有 LineNumberTableSourceFile,code 屬性里的 LocalVariableTable 屬性也沒了(當然,因為編譯時沒加 -parameters 參數(shù),MethodParameters 屬性自然也沒了)。若選擇不生成這兩個屬性,對程序運行產(chǎn)生的最主要的影響就是,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名和出錯的行號,并且在調(diào)試程序的時候,也無法按照源碼行來設(shè)置斷點。

$ javac -d "target/classes" src/main/java/com/test/*.java -g:vars
$ javap -v -cp "target/classes" com.test.TestClass
Classfile /Users/yulewei/IdeaProjects/hellojava/target/classes/com/test/TestClass.class
  Last modified 2017-5-2; size 302 bytes
  MD5 checksum d430f817e0e2cfafc9095279c67aaa72
public class com.test.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#15         // java/lang/Object."":()V
   #2 = Class              #16            // com/test/TestClass
   #3 = Class              #17            // java/lang/Object
   #4 = Utf8               
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = Utf8               LocalVariableTable
   #8 = Utf8               this
   #9 = Utf8               Lcom/test/TestClass;
  #10 = Utf8               sum
  #11 = Utf8               (II)I
  #12 = Utf8               num1
  #13 = Utf8               I
  #14 = Utf8               num2
  #15 = NameAndType        #4:#5          // "":()V
  #16 = Utf8               com/test/TestClass
  #17 = Utf8               java/lang/Object
{
  public com.test.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/test/TestClass;

  public int sum(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lcom/test/TestClass;
            0       4     1  num1   I
            0       4     2  num2   I
}

可以看到,code 屬性里的出現(xiàn)了 LocalVariableTable 屬性,這個屬性保存的就是方法參數(shù)和方法內(nèi)的本地變量。在演示代碼的 sum 方法中沒有定義本地變量,若存在的話,也將會保存在 LocalVariableTable 中。

javap-v 選項會輸出全部反編譯信息,若只想看行號和本地變量信息,改用 -l 即可。輸出結(jié)果如下:

$ javap -l -cp "target/classes" com.test.TestClass
public class com.test.TestClass {
  public com.test.TestClass();
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       5     0  this   Lcom/test/TestClass;

  public int sum(int, int);
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       4     0  this   Lcom/test/TestClass;
          0       4     1  num1   I
          0       4     2  num2   I
}

若要全部生成全部提示信息,編譯參數(shù)需要改為 -g:source,lines,vars。一般在 IDE 下調(diào)試代碼都需要調(diào)試信息,所以這三個參數(shù)默認都會開啟。IDEA 下的 javac 默認參數(shù)設(shè)置,如圖:

若使用 maven,maven 的默認的編譯插件 maven-compiler-plugin 也會默認開啟這三個參數(shù) [doc],經(jīng)實際驗證也包括了LocalVariableTable。

代碼如何實現(xiàn)

上文中講了 class 文件中的調(diào)試信息中 LocalVariableTable 屬性里就包含方法名參數(shù),這就是運行時獲取方法參數(shù)名的方法。讀取這個屬性,JDK 并沒有提供 API,只能借助第三方庫解析 class 文件實現(xiàn)。

要解析 class 文件典型的工具庫有 ObjectWeb 的 ASM(wiki,home,mvn,javadoc)、Apache 的 Commons BCEL(wiki,home,mvn,javadoc)、 日本教授開發(fā)的 Javassist(wiki,github,mvn,javadoc)等。其中 ASM 使用最廣,使用 ASM 的知名開源項目有,AspectJ, CGLIB, Clojure, Groovy, JRuby, Jython, TopLink等等 [ref ]。當然使用 BCEL 的項目也很多 [ref ]。ASM 相對其他庫的 jar 更小,運行速度更快 [javadoc ]。目前 asm-5.0.1.jar 文件大小 53 KB,BCEL 5.2 版本文件大小 520 KB,javassist-3.20.0-GA.jar 文件大小 751 KB。jar 包文件小,自然意味著代碼量更少,提供的功能自然也少了。

BCEL

先來看看用 BCEL 獲取方法參數(shù)名的寫法,代碼如下:

package com.test;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.Type;

public class BcelMain {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        java.lang.reflect.Method m = TestClass.class.getDeclaredMethod("sum", int.class, int.class);
        JavaClass clazz = Repository.lookupClass("com.test.TestClass");
        Method bcelMethod = clazz.getMethod(m);
        LocalVariableTable lvt = bcelMethod.getLocalVariableTable();
        for (LocalVariable lv : lvt.getLocalVariableTable()) {
            System.out.println(lv.getName() + "  " + lv.getSignature() + "  " + Type.getReturnType(lv.getSignature()));
        }
    }
}

輸出結(jié)果:

this  Lcom/test/TestClass;  com.test.TestClass
num1  I  int
num2  I  int
ASM

ASM 的寫法如下:

package com.test;
import org.objectweb.asm.*;

public class AsmMain {

    public static void main(String[] args) throws Exception {
        ClassReader classReader = new ClassReader("com.test.TestClass");
        classReader.accept(new ParameterNameDiscoveringVisitor("sum", "(II)I"), 0);
    }

    private static class ParameterNameDiscoveringVisitor extends ClassVisitor {
        private final String methodName;
        private final String methodDesc;

        public ParameterNameDiscoveringVisitor(String name, String desc) {
            super(Opcodes.ASM5);
            this.methodName = name;
            this.methodDesc = desc;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            if (name.equals(this.methodName) && desc.equals(methodDesc))
                return new LocalVariableTableVisitor();
            return null;
        }
    }

    private static class LocalVariableTableVisitor extends MethodVisitor {

        public LocalVariableTableVisitor() {
            super(Opcodes.ASM5);
        }

        @Override
        public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) {
            System.out.println(name + "  " + description);
        }
    }
}
Spring 框架

若使用 Spring 框架,對于運行時獲取參數(shù)名,Spring 提供了內(nèi)建支持,對應的實現(xiàn)類為 DefaultParameterNameDiscoverer (javadoc)。該類先嘗試用 Java 8 新的反射 API 獲取方法參數(shù)名,若無法獲取,則使用 ASM 庫讀取 class 文件的 LocalVariableTable,對應的代碼分別為 StandardReflectionParameterNameDiscoverer 和 LocalVariableTableParameterNameDiscoverer。

參考資料

2014-10 Java 8 Named Method Parameters https://www.beyondjava.net/bl...

JEP 118: Access to Parameter Names at Runtime http://openjdk.java.net/jeps/118

Enhancements to the Reflection API http://docs.oracle.com/javase...

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

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

相關(guān)文章

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

    摘要:但是這種方式對于接口和抽象方法是不管用的,因為抽象方法沒有方法體,也就沒有局部變量,自然也就沒有局部變量表了是通過接口跟語句綁定然后生成代理類來實現(xiàn)的,因此它無法通過解析字節(jié)碼來獲取方法參數(shù)名。 聲明:本文屬原創(chuàng)文章,首發(fā)于公號:程序員自學之道,轉(zhuǎn)載請注明出處! 發(fā)現(xiàn)問題 對Java字節(jié)碼有一定了解的朋友應該知道,Java 在編譯的時候,默認會將方法參數(shù)名丟棄,因此我們無法在運行時獲取...

    vslam 評論0 收藏0
  • JAVA 8 反射獲取參數(shù)

    摘要:前言在之前編譯是不會把構(gòu)造器和方法的參數(shù)名編譯進中,如果需要獲取參數(shù)名,可以在方法上加上注解,反射獲取注解的值從而獲取參數(shù)名,比如的和。帶在中添加命令行在后面加運行結(jié)果構(gòu)造器方法一方法二方法三方法四這樣就把參數(shù)名給打印出來了,為。 前言 在JDK8之前javac編譯是不會把構(gòu)造器和方法的參數(shù)名編譯進class中,如果需要獲取參數(shù)名,可以在方法上加上注解,反射獲取注解的值從而獲取參數(shù)名,...

    shiweifu 評論0 收藏0
  • Java通過ASM運行讀取方法參數(shù)

    摘要:據(jù)說已經(jīng)原生支持參數(shù)名讀取了。本文以為例進行說明通過字節(jié)碼操作工具我們可以實現(xiàn)運行時參數(shù)名的讀寫。簡單說說原理字節(jié)碼為每個方法保存了一份方法本地變量列表。 據(jù)說Java8已經(jīng)原生支持參數(shù)名讀取了。具體不是很清楚。本文以java7為例進行說明.通過ASM字節(jié)碼操作工具我們可以實現(xiàn)運行時參數(shù)名的讀寫。簡單說說原理:java字節(jié)碼為每個方法保存了一份方法本地變量列表??梢酝ㄟ^ASM獲取這個列...

    DangoSky 評論0 收藏0
  • 1、類加載器 2、反射構(gòu)造方法 3、反射成員變量 4、反射成員方法 5、反射配置文件運行類中的方法

    摘要:通過反射獲取無參構(gòu)造方法并使用得到無參構(gòu)造方法獲取所有的修飾的構(gòu)造方法。如果方法沒有返回值,返回的是反射獲取空參數(shù)成員方法并運行代碼演示反射獲取成員方法并運行獲取對象中的成員方法獲取的是文件中的所有公共成員方法包括繼承的類是描述 01類加載器 * A.類的加載 當程序要使用某個類時,如果該類還未被加載到內(nèi)存中,則系統(tǒng)會通過加載,連接,初始化三步來實現(xiàn)對這個類進行初始化。 ? ...

    Profeel 評論0 收藏0
  • 一文帶你了解Java反射機制

    摘要:在的反射包中提供了三個類以及來分別描述屬性方法和構(gòu)造器。獲取構(gòu)造器獲取方法可以看到我們可以通過一個類的對象很輕松的獲取他的屬性構(gòu)造器以及方法信息。返冋一個用于描述構(gòu)造器名的字符串。 想要獲取更多文章可以訪問我的博客?-?代碼無止境。 上周上班的時候解決一個需求,需要將一批數(shù)據(jù)導出到Excel。本來公司的中間件組已經(jīng)封裝好了使用POI生成Excel的工具方法,但是無奈產(chǎn)品的需求里面有個合...

    darcrand 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<