摘要:原文如果覺得我的文章對你有用,請隨意贊賞本文整理運行時獲取方法參數名的兩種方法,的最新的方法和之前的方法。文件中的調試信息上文介紹了通過新增的反射運行時獲取方法參數名。
原文:http://nullwy.me/2017/04/java...
如果覺得我的文章對你有用,請隨意贊賞
本文整理 Java 運行時獲取方法參數名的兩種方法,Java 8 的最新的方法和 Java 8 之前的方法。
Java 8 的新特性翻閱 Java 8 的新特性,可以看到有這么一條“JEP 118: Access to Parameter Names at Runtime”。這個特性就是為了能運行時獲取參數名新加的。這個 JEP 只是功能增強的提案,并沒有最終實現的 JDK 相關的 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 文檔中關于 -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.
現在試驗下這個特性。有如下兩個文件:
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 編譯,結果如下:
$ javac -d "target/classes" src/main/java/com/test/*.java $ java -cp "target/classes" com.test.Java8Main int arg0 int arg1
加上 -parameters 后,運行結果如下:
$ javac -d "target/classes" -parameters src/main/java/com/test/*.java $ java -cp "target/classes" com.test.Java8Main int num1 int num2
可以看到,加上 -parameters 后,正確獲得了參數名。實際開發中,很少直接用命令行編譯 Java 代碼,項目一般都會用 maven 管理。在 maven 下,只需修改 pom 文件的 maven-compiler-plugin 插件配置即可,就是加上了 compilerArgs 節點 [doc ],如下:
實現原理org.apache.maven.plugins maven-compiler-plugin 1.8 -parameters
“Enhancements in Java SE 8”提到,參數名信息回存儲在 class 文件中?,F在試試用 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"
在結尾的 MethodParameters 屬性就是,實現運行時獲取方法參數的核心。這個屬性是 Java 8 的 class 文件新加的,具體介紹可以參考官方“Java 虛擬機官方”文檔的介紹,“4.7.24. The MethodParameters Attribute”,doc。
class 文件中的調試信息上文介紹了 Java 8 通過新增的反射 API 運行時獲取方法參數名。那么在 Java 8 之前,有沒有辦法呢?或者在編譯時沒有開啟 -parameters 參數,又如何動態獲取方法參數名呢?其實 class 文件中保存的調試信息就可以包含方法參數名。
javac 的 -g 選項可以在 class 文件中生成調試信息,官方文檔介紹如下 [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.
可以看到默認是包含源代碼信息和行號信息的。現在試驗下不生成調試信息的情況:
$ 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 }
對比上文的反編譯結果,可以看到,輸出結果中的 Compiled from "TestClass.java" 沒了,Constant pool 中也不再有 LineNumberTable 和 SourceFile,code 屬性里的 LocalVariableTable 屬性也沒了(當然,因為編譯時沒加 -parameters 參數,MethodParameters 屬性自然也沒了)。若選擇不生成這兩個屬性,對程序運行產生的最主要的影響就是,當拋出異常時,堆棧中將不會顯示出錯代碼所屬的文件名和出錯的行號,并且在調試程序的時候,也無法按照源碼行來設置斷點。
$ 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 屬性里的出現了 LocalVariableTable 屬性,這個屬性保存的就是方法參數和方法內的本地變量。在演示代碼的 sum 方法中沒有定義本地變量,若存在的話,也將會保存在 LocalVariableTable 中。
javap 的 -v 選項會輸出全部反編譯信息,若只想看行號和本地變量信息,改用 -l 即可。輸出結果如下:
$ 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 }
若要全部生成全部提示信息,編譯參數需要改為 -g:source,lines,vars。一般在 IDE 下調試代碼都需要調試信息,所以這三個參數默認都會開啟。IDEA 下的 javac 默認參數設置,如圖:
若使用 maven,maven 的默認的編譯插件 maven-compiler-plugin 也會默認開啟這三個參數 [doc],經實際驗證也包括了LocalVariableTable。
代碼如何實現上文中講了 class 文件中的調試信息中 LocalVariableTable 屬性里就包含方法名參數,這就是運行時獲取方法參數名的方法。讀取這個屬性,JDK 并沒有提供 API,只能借助第三方庫解析 class 文件實現。
要解析 class 文件典型的工具庫有 ObjectWeb 的 ASM(wiki,home,mvn,javadoc)、Apache 的 Commons BCEL(wiki,home,mvn,javadoc)、 日本教授開發的 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 獲取方法參數名的寫法,代碼如下:
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())); } } }
輸出結果:
this Lcom/test/TestClass; com.test.TestClass num1 I int num2 I intASM
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 框架,對于運行時獲取參數名,Spring 提供了內建支持,對應的實現類為 DefaultParameterNameDiscoverer (javadoc)。該類先嘗試用 Java 8 新的反射 API 獲取方法參數名,若無法獲取,則使用 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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74630.html
摘要:但是這種方式對于接口和抽象方法是不管用的,因為抽象方法沒有方法體,也就沒有局部變量,自然也就沒有局部變量表了是通過接口跟語句綁定然后生成代理類來實現的,因此它無法通過解析字節碼來獲取方法參數名。 聲明:本文屬原創文章,首發于公號:程序員自學之道,轉載請注明出處! 發現問題 對Java字節碼有一定了解的朋友應該知道,Java 在編譯的時候,默認會將方法參數名丟棄,因此我們無法在運行時獲取...
摘要:前言在之前編譯是不會把構造器和方法的參數名編譯進中,如果需要獲取參數名,可以在方法上加上注解,反射獲取注解的值從而獲取參數名,比如的和。帶在中添加命令行在后面加運行結果構造器方法一方法二方法三方法四這樣就把參數名給打印出來了,為。 前言 在JDK8之前javac編譯是不會把構造器和方法的參數名編譯進class中,如果需要獲取參數名,可以在方法上加上注解,反射獲取注解的值從而獲取參數名,...
摘要:據說已經原生支持參數名讀取了。本文以為例進行說明通過字節碼操作工具我們可以實現運行時參數名的讀寫。簡單說說原理字節碼為每個方法保存了一份方法本地變量列表。 據說Java8已經原生支持參數名讀取了。具體不是很清楚。本文以java7為例進行說明.通過ASM字節碼操作工具我們可以實現運行時參數名的讀寫。簡單說說原理:java字節碼為每個方法保存了一份方法本地變量列表??梢酝ㄟ^ASM獲取這個列...
摘要:通過反射獲取無參構造方法并使用得到無參構造方法獲取所有的修飾的構造方法。如果方法沒有返回值,返回的是反射獲取空參數成員方法并運行代碼演示反射獲取成員方法并運行獲取對象中的成員方法獲取的是文件中的所有公共成員方法包括繼承的類是描述 01類加載器 * A.類的加載 當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,連接,初始化三步來實現對這個類進行初始化。 ? ...
摘要:在的反射包中提供了三個類以及來分別描述屬性方法和構造器。獲取構造器獲取方法可以看到我們可以通過一個類的對象很輕松的獲取他的屬性構造器以及方法信息。返冋一個用于描述構造器名的字符串。 想要獲取更多文章可以訪問我的博客?-?代碼無止境。 上周上班的時候解決一個需求,需要將一批數據導出到Excel。本來公司的中間件組已經封裝好了使用POI生成Excel的工具方法,但是無奈產品的需求里面有個合...
閱讀 1993·2021-11-24 10:45
閱讀 1850·2021-10-09 09:43
閱讀 1291·2021-09-22 15:38
閱讀 1219·2021-08-18 10:19
閱讀 2837·2019-08-30 15:55
閱讀 3057·2019-08-30 12:45
閱讀 2962·2019-08-30 11:25
閱讀 356·2019-08-29 11:30