java如何擷取方法參數名

來源:互聯網
上載者:User

標籤:java參數名   java擷取參數名   方法參數名   參數名稱   

在java中,可以通過反射擷取到類、欄位、方法簽名等相關的資訊,像方法名、返回值類型、參數類型、泛型型別參數等,但是不能夠擷取方法的參數名。在實際開發情境中,有時需要根據方法的參數名做一些操作,比如像spring-mvc中,@RequestParam、@PathVariable註解,如果不指定相應的value屬性,預設就是使用方法的參數名做為HTTP請求的參數名,它是怎麼做到的呢?

在這樣情況下,有兩種方法擷取方法來解決這種需求,第一種方法是使用註解,在註解中指定對應應的參數名稱,在需要使用參數名稱時,擷取註解中相應的值即可。第二種方法是從位元組碼中擷取方法的參數名,但是這有一個限制,只有在編譯時間使用了-g或-g:vars參數產生了調試資訊,class檔案中才會產生方法參數名資訊(在本地變數表LocalVariableTable中),而使用-g:none方式編譯的class檔案中是沒有方法參數名資訊的。所以要想完全不依賴class檔案的編譯模式,就不能使用這種方式。下面討論一下兩方式的實現。

一、從註解中擷取

使用註解方式,我們需要自訂一個註解,在註解中指定參數名,然後通過反射機制,擷取方法參數上的註解,從而擷取到相應的註解資訊。這裡自訂的註解是Param,通過value參數指定參數名,定義了一個工具類ParameterNameUtils來擷取指定方法的參數名列表,這裡擷取測試類別ParameterNameTest中定義的方法method1的參數名列表表,下面是具體的代碼。

首先定義註解:

package com.mikan;import java.lang.annotation.*;/** * @author Mikan * @date 2015-08-04 23:39 */@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Param {    String value();}

擷取註解中的參數名的工具類:

package com.mikan;import java.lang.annotation.Annotation;import java.lang.reflect.Method;/** * @author Mikan * @date 2015-08-05 00:26 */public class ParameterNameUtils {    /**     * 擷取指定方法的參數名     *     * @param method 要擷取參數名的方法     * @return 按參數順序排列的參數名列表     */    public static String[] getMethodParameterNamesByAnnotation(Method method) {        Annotation[][] parameterAnnotations = method.getParameterAnnotations();        if (parameterAnnotations == null || parameterAnnotations.length == 0) {            return null;        }        String[] parameterNames = new String[parameterAnnotations.length];        int i = 0;        for (Annotation[] parameterAnnotation : parameterAnnotations) {            for (Annotation annotation : parameterAnnotation) {                if (annotation instanceof Param) {                    Param param = (Param) annotation;                    parameterNames[i++] = param.value();                }            }        }        return parameterNames;    }}

測試類別:

package com.mikan;import java.lang.reflect.Method;import java.util.Arrays;/** * @author Mikan * @date 2015-08-04 23:40 */public class ParameterNameTest {    public void method1(@Param("parameter1") String param1, @Param("parameter2") String param2) {        System.out.println(param1 + param2);    }    public static void main(String[] args) throws Exception {        Class<ParameterNameTest> clazz = ParameterNameTest.class;        Method method = clazz.getDeclaredMethod("method1", String.class, String.class);        String[] parameterNames = ParameterNameUtils.getMethodParameterNamesByAnnotation(method);        System.out.println(Arrays.toString(parameterNames));    }}

輸出結果:

[parameter1, parameter2]

二、從class檔案中擷取

預設情況下,javac不會產生本地變數表資訊,只會產生行號表資訊,如果要產生本地變數表資訊,需要指定-g或-g:vars參數,如果要產生本地變數表和行號表資訊,可以使用-g:vars,lines參數。首先看一下使用-g參數和-g:none參數產生的class檔案的區別,再討論如何擷取。

測試類別如下:

package com.mikan;/** * @author Mikan * @date 2015-08-04 23:40 */public class ParameterNameTest1 {    public void method1(String param1, String param2) {        System.out.println(param1 + param2);    }}

使用-g參數產生調試資訊:

javac -g -d /Users/mikan/Documents/workspace/project/algorithm/target/classes /Users/mikan/Documents/workspace/project/algorithm/src/main/java/com/mikan/*.java

通過javap查看位元組碼:

localhost:mikan mikan$ javap -c -v ParameterNameTest1.classClassfile /Users/mikan/Documents/workspace/project/algorithm/target/classes/com/mikan/ParameterNameTest1.class  Last modified 2015-8-5; size 771 bytes  MD5 checksum 1c7617df9da5106249ab744feae684d1  Compiled from "ParameterNameTest1.java"public class com.mikan.ParameterNameTest1  SourceFile: "ParameterNameTest1.java"  minor version: 0  major version: 51  flags: ACC_PUBLIC, ACC_SUPERConstant pool:   #1 = Methodref          #9.#24         //  java/lang/Object."<init>":()V   #2 = Fieldref           #25.#26        //  java/lang/System.out:Ljava/io/PrintStream;   #3 = Class              #27            //  java/lang/StringBuilder   #4 = Methodref          #3.#24         //  java/lang/StringBuilder."<init>":()V   #5 = Methodref          #3.#28         //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;   #6 = Methodref          #3.#29         //  java/lang/StringBuilder.toString:()Ljava/lang/String;   #7 = Methodref          #30.#31        //  java/io/PrintStream.println:(Ljava/lang/String;)V   #8 = Class              #32            //  com/mikan/ParameterNameTest1   #9 = Class              #33            //  java/lang/Object  #10 = Utf8               <init>  #11 = Utf8               ()V  #12 = Utf8               Code  #13 = Utf8               LineNumberTable  #14 = Utf8               LocalVariableTable  #15 = Utf8               this  #16 = Utf8               Lcom/mikan/ParameterNameTest1;  #17 = Utf8               method1  #18 = Utf8               (Ljava/lang/String;Ljava/lang/String;)V  #19 = Utf8               param1  #20 = Utf8               Ljava/lang/String;  #21 = Utf8               param2  #22 = Utf8               SourceFile  #23 = Utf8               ParameterNameTest1.java  #24 = NameAndType        #10:#11        //  "<init>":()V  #25 = Class              #34            //  java/lang/System  #26 = NameAndType        #35:#36        //  out:Ljava/io/PrintStream;  #27 = Utf8               java/lang/StringBuilder  #28 = NameAndType        #37:#38        //  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  #29 = NameAndType        #39:#40        //  toString:()Ljava/lang/String;  #30 = Class              #41            //  java/io/PrintStream  #31 = NameAndType        #42:#43        //  println:(Ljava/lang/String;)V  #32 = Utf8               com/mikan/ParameterNameTest1  #33 = Utf8               java/lang/Object  #34 = Utf8               java/lang/System  #35 = Utf8               out  #36 = Utf8               Ljava/io/PrintStream;  #37 = Utf8               append  #38 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;  #39 = Utf8               toString  #40 = Utf8               ()Ljava/lang/String;  #41 = Utf8               java/io/PrintStream  #42 = Utf8               println  #43 = Utf8               (Ljava/lang/String;)V{  public com.mikan.ParameterNameTest1();    flags: ACC_PUBLIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."<init>":()V         4: return      LineNumberTable:        line 7: 0      LocalVariableTable:        Start  Length  Slot  Name   Signature               0       5     0  this   Lcom/mikan/ParameterNameTest1;  public void method1(java.lang.String, java.lang.String);    flags: ACC_PUBLIC    Code:      stack=3, locals=3, args_size=3         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: new           #3                  // class java/lang/StringBuilder         6: dup         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V        10: aload_1        11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        14: aload_2        15: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        18: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        21: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V        24: return      LineNumberTable:        line 10: 0        line 11: 24      LocalVariableTable:        Start  Length  Slot  Name   Signature               0      25     0  this   Lcom/mikan/ParameterNameTest1;               0      25     1 param1   Ljava/lang/String;               0      25     2 param2   Ljava/lang/String;}localhost:mikan mikan$
關於位元組碼檔案的結構及各部分代表的含義,這裡就不一一說明了,如果有需要可以查看相關的資料。

其中最後一部分,從public void method1(java.lang.String, java.lang.String);開始是方法method1的位元組碼資訊,可以從最後幾行看到:

      LocalVariableTable:        Start  Length  Slot  Name   Signature               0      25     0  this   Lcom/mikan/ParameterNameTest1;               0      25     1 param1   Ljava/lang/String;               0      25     2 param2   Ljava/lang/String;
這幾行表示本地變數表資訊,可以看到method1方法有3個參數,為什麼會有3個參數呢?還記得在執行個體方法中我們可以使用this來表示當前執行個體麼,這就是為什麼,因為在編譯時間編譯器自動給我們添加了一個參數代表當前執行個體,而且它是第一個參數,另外可以看到param1和param2,這就是方法聲明中的參數名。既然位元組碼中有方法參數名的資訊,那麼我們就可以通過某種方式從class檔案中擷取這些資訊。

另外還可以注意到,我們源碼中System.out.println(param1 + param2);列印兩個字串參數串連,在編譯時間編譯器自動給最佳化成了使用StringBuilder的方式,像這種類似的編譯器最佳化還有很多^_^。

下面來看一下不使用-g參數或使用-g:none參數編譯後的class檔案的格式,這裡只顯示方法method1的位元組碼,其他的省略:

localhost:mikan mikan$ javac -g:none -d /Users/mikan/Documents/workspace/project/algorithm/target/classes /Users/mikan/Documents/workspace/project/algorithm/src/main/java/com/mikan/*.javalocalhost:mikan mikan$ javap -c -v ParameterNameTest1.class  public void method1(java.lang.String, java.lang.String);    flags: ACC_PUBLIC    Code:      stack=3, locals=3, args_size=3         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;         3: new           #3                  // class java/lang/StringBuilder         6: dup         7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V        10: aload_1        11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        14: aload_2        15: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;        18: invokevirtual #6                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;        21: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V        24: return
從產生的位元組碼可以看到,在使用-g:none參數後不再產生本地變數表,所以也就沒有方法的參數名等資訊,所以這種情況下就不能使用這種方式來擷取方法參數名了,只能使用前一種方法。

要從位元組碼中擷取方法的參數名資訊,就需要解析字碼碼檔案,這要求對位元組碼檔案很熟悉才行,這是一個很複雜的工作。還好我們可以使用第三方的類庫像asm、javassist來操作。這裡使用asm4.0。

代碼如下:

package com.mikan;import org.objectweb.asm.*;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import java.util.Arrays;/** * @author Mikan * @date 2015-08-05 00:26 */public class ParameterNameUtils {    /**     * 擷取指定類指定方法的參數名     *     * @param clazz 要擷取參數名的方法所屬的類     * @param method 要擷取參數名的方法     * @return 按參數順序排列的參數名列表,如果沒有參數,則返回null     */    public static String[] getMethodParameterNamesByAsm4(Class<?> clazz, final Method method) {        final Class<?>[] parameterTypes = method.getParameterTypes();        if (parameterTypes == null || parameterTypes.length == 0) {            return null;        }        final Type[] types = new Type[parameterTypes.length];        for (int i = 0; i < parameterTypes.length; i++) {            types[i] = Type.getType(parameterTypes[i]);        }        final String[] parameterNames = new String[parameterTypes.length];        String className = clazz.getName();        int lastDotIndex = className.lastIndexOf(".");        className = className.substring(lastDotIndex + 1) + ".class";        InputStream is = clazz.getResourceAsStream(className);        try {            ClassReader classReader = new ClassReader(is);            classReader.accept(new ClassVisitor(Opcodes.ASM4) {                @Override                public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {                    // 只處理指定的方法                    Type[] argumentTypes = Type.getArgumentTypes(desc);                    if (!method.getName().equals(name) || !Arrays.equals(argumentTypes, types)) {                        return null;                    }                    return new MethodVisitor(Opcodes.ASM4) {                        @Override                        public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {                            // 靜態方法第一個參數就是方法的參數,如果是執行個體方法,第一個參數是this                            if (Modifier.isStatic(method.getModifiers())) {                                parameterNames[index] = name;                            }                            else if (index > 0) {                                parameterNames[index - 1] = name;                            }                        }                    };                }            }, 0);        } catch (IOException e) {            e.printStackTrace();        }        return parameterNames;    }}

測試類別:

package com.mikan;import java.lang.reflect.Method;import java.util.Arrays;/** * @author Mikan * @date 2015-08-04 23:40 */public class ParameterNameTest {    public void method1(String param1, String param2) {        System.out.println(param1 + param2);    }    public static void main(String[] args) throws Exception {        Class<ParameterNameTest> clazz = ParameterNameTest.class;        Method method = clazz.getDeclaredMethod("method1", String.class, String.class);        String[] parameterNames = ParameterNameUtils.getMethodParameterNamesByAsm4(clazz, method);        System.out.println(Arrays.toString(parameterNames));    }}
輸出結果:

[param1, param2]


著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

java如何擷取方法參數名

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.