美文网首页JavaJava In Mind
Java8增强反射可以在运行时获取参数名

Java8增强反射可以在运行时获取参数名

作者: SevenLin1993 | 来源:发表于2019-10-22 22:48 被阅读0次

    技术公众号:Java In Mind(Java_In_Mind),欢迎关注!
    原文:Java8增强反射可以在运行时获取参数名

    介绍

    在JDK增强意见:JPE 118:Access to Parameter Names at Runtime中指出,在Java8中我们终于可以通过反射来获取方法的参数名,其主要的目的是:

    • 提高代码的可读性(原先通常使用注解来实现)
    • 可以提高IDE的功能

    JDK8前获取参数名的方法

    通过注解来实现

    因为Java8之前不提供获取参数名称的功能,大部分实现都是通过提供注解元数据来标明参数名,这个也是目前许多框架使用的方法,如,SpringMVC的参数绑定,MyBatis的参数映射,类似:

    //自定义@param注解
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Param {
        String value();
    }
    //声明参数名
    public void foo(@Param("name") String name, @Param("count") int count){
         System.out.println("name:=" + name + ",count=" + count);
    }
    
    //获取
    Method foo = ParameterDemo.class.getMethod("foo", String.class, int.class);
    
    Annotation[][] parameterAnnotations = foo.getParameterAnnotations();
    
    for (Annotation[] parameterAnnotation : parameterAnnotations) {
        for (Annotation annotation : parameterAnnotation) {
            if(annotation instanceof Param){
                System.out.println(((Param) annotation).value());
            }
        }
    }
    //获取结果
    name
    count
    
    通过解析class文件

    可以通过解析二进制文件来获取参数的名称,常见的工具有:ASM,javassist,如,Spring的LocalVariableTableParameterNameDiscoverer,就是利用ASM通过class文件中的本地方法变量表中获取到参数名称:

    //使用Spring的LocalVariableTableParameterNameDiscoverer获取
    public void foo(String name, int count){
        System.out.println("name:=" + name + ",count=" + count);
    }
    
    Method foo = ParameterDemo.class.getMethod("foo", String.class, int.class);
    String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(foo);
            System.out.println(Arrays.toString(parameterNames));
    
    //获取结果
    [name, count]
    

    使用Java8反射

    JDK8在反射包中新增了Parameter类,用于表示方法的参数信息,通过Method来获取所有参数列表:

    public void foo(String name, int count){
        System.out.println("name:=" + name + ",count=" + count);
    }
    //通过反射获取
    Method foo = ParameterDemo.class.getMethod("foo", String.class, int.class);
    Parameter[] parameters = foo.getParameters();
    for (Parameter parameter : parameters) {
        System.out.println(parameter.getName());
    }
    //获取结果
    name
    count
    

    【注意】

    该功能需要在javac编译时开启-parameters参数,而为了兼容性该参数默认是不开启的,如果使用Maven构建的话可以如此配置:

    <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
            <source>8</source>
            <target>8</target>
            <compilerArgs>
                <compilerArg>-parameters</compilerArg>
            </compilerArgs>
         </configuration>
    </plugin>
    

    【原理】

    通过反编译可以知道,class文件保存了MethodParameters的信息,这就是保存方法名的地方:

      //截取片段
      public void foo(java.lang.String, int);
        descriptor: (Ljava/lang/String;I)V
        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: ldc           #5                  // String name:=
            12: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            15: aload_1
            16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            19: ldc           #7                  // String ,count=
            21: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: iload_2
            25: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
            28: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            31: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            34: return
          LineNumberTable:
            line 14: 0
            line 15: 34
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      35     0  this   Lcom/sevenlin/demo/reflect/ParameterDemo;
                0      35     1  name   Ljava/lang/String;
                0      35     2 count   I
        MethodParameters:
          Name                           Flags
          name
          count
    

    总结

    Java运行时是使用位置解析参数的,但随着应用的发展,常常需要获取参数名的功能,如一些IDE的反编译功能,由于Java8之前不支持参数名的获取,通常都是通过别的方法来实现,通常就是通过注解,这会使代码比较混乱,而且冗余,降低代码阅读性,而通过解析class文件的方式来获取比较麻烦,而且也不提倡。

    Java8对反射的增加也是为了解决以上的问题,而使用的时候注意要在编译时开启。

    相关文章

      网友评论

        本文标题:Java8增强反射可以在运行时获取参数名

        本文链接:https://www.haomeiwen.com/subject/oyncvctx.html