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选项
### 通过 `java -jar` 启动时指定实例参数 在使用 `java -jar` 命令启动 Java 应用时,无法直接通过 JVM 参数指定“实例”这一概念。然而,可以通过应用程序的启动参数将实例传递给应用,并由应用自身进行识别和处理。这种机制在微服务架构中非常常见,例如 Spring Boot 应用可以通过自定义参数来标识不同的服务实例。 #### 1. 使用自定义命令行参数传递实例 Java 应用程序的 `main` 方法接收一个 `String[] args` 参数开发者可以在启动时通过命令行传递自定义参数,例如: ```bash java -jar myapp.jar --instance-name=my-instance-01 ``` 在 Spring Boot 应用中,该参数可以通过 `@Value` 注解注到配置类或服务类中,也可以通过 `SpringApplication` 的 `setDefaultProperties` 方法进行设置: ```java @SpringBootApplication public class MyApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(MyApplication.class); app.setDefaultProperties(Collections .singletonMap("spring.application.instance", "my-instance-01")); app.run(args); } } ``` 该方式将实例作为 Spring 应用上下文的一部分,可以在日志、监控、服务注册等场景中使用[^1]。 #### 2. 通过环境变量设置实例 除了命令行参数外,还可以通过环境变量来传递实例。例如在 Linux 系统中: ```bash INSTANCE_NAME=my-instance-01 java -jar myapp.jar ``` 在 Spring Boot 应用中,可以通过 `Environment` 对象获取该变量: ```java @Value("${INSTANCE_NAME}") private String instanceName; ``` 这种方式适用于容器化部署场景,例如在 Kubernetes 或 Docker 中通过环境变量注实例标识。 #### 3. 结合 JVM 参数设置系统属性 虽然 JVM 本身不支持“实例”这一参数,但可以通过 `-D` 参数设置系统属性,例如: ```bash java -Dinstance.name=my-instance-01 -jar myapp.jar ``` 在应用中可以通过 `System.getProperty("instance.name")` 获取该值,或者通过 Spring 的 `Environment` 获取: ```java @Value("${instance.name}") private String instanceName; ``` 该方法适用于需要在 JVM 层面进行标识的场景,例如日志文件命、JMX 监控等。 #### 4. Spring Boot 中的实例配置 在 Spring Boot 应用中,可以通过 `application.properties` 或 `application.yml` 显式配置实例: ```properties spring.application.name=myapp spring.application.instance=my-instance-01 ``` 该配置将影响服务注册到 Spring Cloud Eureka、Consul 或 Zookeeper 时的实例标识,有助于服务发现和负载均衡。 #### 5. 使用 Spring Cloud 提供的 `spring.cloud.client.instance-id` 在 Spring Cloud 应用中,可以通过以下方式自定义注册到服务发现组件的实例 ID: ```properties spring.cloud.client.instance-id=myapp-my-instance-01 ``` 该配置将覆盖默认的实例 ID(通常是主机+端口),在服务注册和健康检查中具有重要作用。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值