Java - 获取方法的参数名

普通情况下无法拿到方法的参数名

.java文件属于源码文件,它需要经过了javac编译器编译为.class字节码文件才能被JVM执行的

Java在编译的时候对于方法,默认是不会保留方法参数名,因此如果我们在运行期想从.class字节码里直接拿到方法的参数名是做不到的

public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test.class.getMethod("test", String.class, Integer.class);
        int parameterCount = method.getParameterCount();
        Parameter[] parameters = method.getParameters();

        System.out.println("方法参数总数:" + parameterCount);
        Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
    }

    public void test(String name, Integer age) {
    }
}

 方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1


框架中好像能拿到参数名?

按照上面的例子,我们在运行期想从.class字节码里直接拿到方法的参数名是做不到的

但是联想起来在使用Spring MVC的时候,即使不使用注解,只要参数名和请求参数的key对应上了,就能自动完成数值的封装。

而在使用MyBatis(接口模式)时,接口方法向xml里的SQL语句传参时,必须(当然不是100%的必须,特殊情况此处不做考虑)使用@Param(’’)指定key值,在SQL中才可以取到


SpringMVC有什么秘技

public class Main {

    public String testArgName(String name, Integer age) {
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Main.class.getMethod("testArgName", String.class, Integer.class);
        int parameterCount = method.getParameterCount();
        Parameter[] parameters = method.getParameters();
        
        System.out.println("方法参数总数:" + parameterCount);
        Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));

        // MethodParameter,DefaultParameterNameDiscoverer,ParameterNameDiscoverer
        // 都是org.springframework.core包下的类
        MethodParameter nameParameter = new MethodParameter(method, 0);
        MethodParameter ageParameter = new MethodParameter(method, 1);
        ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        nameParameter.initParameterNameDiscovery(parameterNameDiscoverer);
        ageParameter.initParameterNameDiscovery(parameterNameDiscoverer);
        System.out.println(nameParameter.getParameterType() + "----" + nameParameter.getParameterName());
        System.out.println(ageParameter.getParameterType() + "----" + ageParameter.getParameterName());
    }
}

方法参数总数:2
class java.lang.String----arg0
class java.lang.Integer----arg1
class java.lang.String----name
class java.lang.Integer----age 

 Spring MVC借助ParameterNameDiscoverer完成了方法参数名的获取,进而完成数据封装


LineNumberTable

线上程序抛出异常时显示的行号,为啥就恰好就是你源码的那一行呢?有这疑问是因为JVM执行的是.class文件,而该文件的行和.java源文件的行肯定是对应不上的,为何行号却能在.java文件里对应上?

LineNumberTable属性存在于代码的字节码中, 它建立了字节码偏移量到源代码行号之间的联系

javap -verbose xxx.class查看字节码信息

依次执行javac xxx.java 和 javap -verbose xxx.class 查看字节码信息

方法中有个LineNumberTable,可以看到,line行号和源码处完全一样,这就解答了我们上面的行号对应的疑问了:LineNumberTable它记录着在源代码处的行号。

public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test.class.getMethod("test", String.class, Integer.class);
        int parameterCount = method.getParameterCount();
        Parameter[] parameters = method.getParameters();

        System.out.println("方法参数总数:" + parameterCount);
        Arrays.stream(parameters).forEach(p -> System.out.println(p.getType() + "----" + p.getName()));
    }

    public void test(String name, Integer age) {
    }
}
// 前方省略。。。
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException;
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
        stack=6, locals=4, args_size=1
        0: ldc           #2                  // class com/example/demo/demos/Test
        2: ldc           #3                  // String test
        // 这块省略。。。
        66: invokeinterface #20,  2           // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
        71: return
        LineNumberTable:
        line 9: 0
        line 10: 22
        line 11: 27
        line 13: 32
        line 14: 57
        line 15: 71
        Exceptions:
        throws java.lang.NoSuchMethodException

public void test(java.lang.String, java.lang.Integer);
        // 这里省略。。。
        LineNumberTable:
        line 18: 0
        }
// 后方省略


LocalVariableTable

LocalVariableTable属性建立了方法中的局部变量与源代码中的局部变量之间的对应关系。这个属性也是存在于代码的字节码中
从名字可以看出来:它是局部变量的一个集合。描述了局部变量和描述符以及和源代码的对应关系。

依次执行javac xxx.java 和 javap -verbose xxx.class 查看字节码信息

可以看到有LineNumberTable,并没有LocalVariableTable

改成分别使用javac -parameters、javac -g来编译后再执行,运行发现程序中能够获得参数名了,查看字节码:

-parameters:

// 前方省略。。。
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException;
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
        stack=6, locals=4, args_size=1
        0: ldc           #2                  // class com/example/demo/demos/Test
        2: ldc           #3                  // String test
        // 这块省略。。。
        66: invokeinterface #20,  2           // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
        71: return
        LineNumberTable:
        line 9: 0
        line 10: 22
        line 11: 27
        line 13: 32
        line 14: 57
        line 15: 71
        Exceptions:
        throws java.lang.NoSuchMethodException
        MethodParameters:
        Name                           Flags
        args

public void test(java.lang.String, java.lang.Integer);
        descriptor: (Ljava/lang/String;Ljava/lang/Integer;)V
        flags: ACC_PUBLIC
        Code:
        stack=0, locals=3, args_size=3
        0: return
        LineNumberTable:
        line 18: 0
        MethodParameters:
        Name                           Flags
        name
        age
        }
// 后方省略。。。

-g: 

// 前方省略。。。
public static void main(java.lang.String[]) throws java.lang.NoSuchMethodException;
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
        stack=6, locals=4, args_size=1
        0: ldc           #2                  // class com/example/demo/demos/Test
        2: ldc           #3                  // String test
        // 这块省略。。。
        66: invokeinterface #20,  2           // InterfaceMethod java/util/stream/Stream.forEach:(Ljava/util/function/Consumer;)V
        71: return
        LineNumberTable:
        line 9: 0
        line 10: 22
        line 11: 27
        line 13: 32
        line 14: 57
        line 15: 71
        LocalVariableTable:
        Start  Length  Slot  Name   Signature
        0      72     0  args   [Ljava/lang/String;
        22      50     1 method   Ljava/lang/reflect/Method;
        27      45     2 parameterCount   I
        32      40     3 parameters   [Ljava/lang/reflect/Parameter;
        Exceptions:
        throws java.lang.NoSuchMethodException

public void test(java.lang.String, java.lang.Integer);
        descriptor: (Ljava/lang/String;Ljava/lang/Integer;)V
        flags: ACC_PUBLIC
        Code:
        stack=0, locals=3, args_size=3
        0: return
        LineNumberTable:
        line 18: 0
        LocalVariableTable:
        Start  Length  Slot  Name   Signature
        0       1     0  this   Lcom/example/demo/demos/Test;
        0       1     1  name   Ljava/lang/String;
        0       1     2   age   Ljava/lang/Integer;
        }
// 后方省略。。。

这里多了一个LocalVariableTable,即局部变量表,就记录着我们方法入参的形参名字。既然记录着了,这样我们就可以通过分析字节码信息来得到这个名称了


获取方法参数名的方式

虽然Java编译器默认情况下会抹去方法的参数名,但有上面介绍了字节码的相关知识可知,我们还是有方法来得到方法的参数名的。

方法一:使用-parameters(最简单直接)
java8原生支持,直接通过java.lang.reflect.Parameter就能获取到

方法二:借助ASM(推荐)

它是一个Java字节码操控框架,它能被用来动态生成类或者增强既有类的功能,它能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。
这样我们就可以通过ASM来分析字节码信息中的本地变量表获取方法参数名了。


总结

为什么我们使用的框架中,比如Spring MVC可以获取方法参数名

spring-core中有个ParameterNameDiscoverer就是用来获取参数名的,底层用的是asm解析,但是接口方法的参数名无法得到,即只能是非接口类的方法参数名可以

可是平时我们开发spring MVC项目都没有显式添加-g编译参数Spring MVC是怎么获取方法参数的?
因为,maven默认是添加了-g选项的

<plugins>
  <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.0</version>
    <configuration>
      <debug>true</debug>     //默认,无需显式配置
      <compilerArgument>-g</compilerArgument>  //默认,无需显式配置
    </configuration>
  </plugin>
</plugins>
  • 1.要想在运行时获取方法的参数名,首先class文件中得有参数名才可以
  • 2.spring mvc底层是通过asm获取的字节码中的方法参数名
  • 3.javac可以通过添加-g或者-parameter选项在class文件中添加参数信息
  • 4.maven编译插件默认添加了-g选项
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值