标签: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) @Documented public @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.class Classfile /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_SUPER Constant 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/*.java localhost: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参数名 java获取参数名 方法参数名 参数名称
原文地址:http://blog.csdn.net/mhmyqn/article/details/47294485