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