欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Java通过反射获取方法参数名的方式小结,

来源: javaer 分享于  点击 12292 次 点评:177

Java通过反射获取方法参数名的方式小结,


目录
  • 1、前言
  • 2、解决方式
    • 方式2.1: 添加编译参数配置 -parameters
    • 方式2.2: 使用Spring的内部工具类 - ParameterNameDiscoverer
  • 使用总结

    1、前言

    一般当我们用类似下面的反射去获取方法参数名时得到的一般是 arg0、arg1, 参数名默认会丢失, 导致无法获取到真实的方法参数名,下面介绍如何通过其他方式解决。

        class A {
            void getUser(String userName, String userId){}
        }
        
        Method method = A.class.getMethod(String.class,String.class);
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            String name = parameter.getName();
            System.out.println(name); // 得到的是arg0, arg1 而非 userName、userId
        }

    2、解决方式

    方式2.1: 添加编译参数配置 -parameters

    java默认在编译工程代码为class文件时,会将方法参数名擦除,替换成arg0、arg1之类。但是我们可以在编译时配置将参数名也一并编译进去,在maven中新增如下编译插件配置

                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <!-- 配置编译时将方法参数名保留 -->
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
    

    对于较新的 maven 版本(>= 3.6.2), 也可以直接使用 parameters 配置项:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
            <parameters>true</parameters>
        </configuration>
    </plugin>
    

    添加-parameters的编译参数后,可以看到我们编译后的class文件是源码的参数名,而非原来的arg0、arg1。 之后我们用原生的反射Parameter.getName() 就可以获取到的方法参数的真实参数名了

    注意: 该方式仅对JDK8及以上版本有效, 以前版本JDK没有提供该保留机制

    方式2.2: 使用Spring的内部工具类 - ParameterNameDiscoverer

    如果正好你的项目依赖了spring,则可以直接使用spring的内部工具类ParameterNameDiscoverer去获取到方法真实参数名。ParameterNameDiscoverer的实现类 主要包含 LocalVariableTableParameterNameDiscoverer、StandardReflectionParameterNameDiscoverer 。 下面介绍如何使用

    1、StandardReflectionParameterNameDiscoverer的使用

    该发现器需要与 方式2.1: 添加编译参数配置 -parameters 一样添加编译配置, 因为底层也是直接用反射Parameter去获取而已

      // 创建参数名发现器
      ParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
    
      Method method = A.class.getMethod(String.class,String.class);
    
      // 获取方法真实参数名.  userName, userId
      String[] parameterNames = discoverer.getParameterNames(method);
    

    2、LocalVariableTableParameterNameDiscoverer的使用

      // 创建参数名发现器
      ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
    
      Method method = A.class.getMethod(String.class,String.class);
    
      // 获取方法真实参数名.  userName, userId
      String[] parameterNames = discoverer.getParameterNames(method);
    

    该发现器底层原理是在编译类时生成一个调试信息的局部变量表LocalVariableTable,然后基于ASM字节码技术去解析LocalVariableTable得到参数名, 所以它也需要在编译时额外添加 编译配置, 请在maven中添加如下-g的编译参数配置

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <compilerArgs>
                            <!-- 生成局部变量表 -->
                            <arg>-g:vars</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    

    该方法优点是JDK8以下版本也能使用,但是缺点是无法获取到接口的方法参数名

    3、DefaultParameterNameDiscoverer的使用

      // 创建参数名发现器
      ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
    
      Method method = A.class.getMethod(String.class,String.class);
    
      // 获取方法真实参数名.  userName, userId
      String[] parameterNames = discoverer.getParameterNames(method);

    这个参数名发现器是一个组合模式的发现器, 本身不去实现获取参数名的逻辑,而是组合其他参数名发现器, 然后迭代调用每个参数名发现器直到找到真实参数名。 从下面源码来看 主要内置了前面讲到的两种StandardReflectionParameterNameDiscoverer 和 LocalVariableTableParameterNameDiscoverer。 所以如果要让对应的参数名发现器生效,需要配合对应发现器的使用逻辑 否则一样无法获取到方法参数名。

    // 源码
    public class DefaultParameterNameDiscoverer extends PrioritizedParameterNameDiscoverer {
    
    	public DefaultParameterNameDiscoverer() {
    		if (!GraalDetector.inImageCode()) {
    			if (KotlinDetector.isKotlinReflectPresent()) {
    				addDiscoverer(new KotlinReflectionParameterNameDiscoverer());
    			}
    			addDiscoverer(new StandardReflectionParameterNameDiscoverer()); // 依赖编译参数 -parameters
    			addDiscoverer(new LocalVariableTableParameterNameDiscoverer()); //  // 依赖编译参数 -g
    		}
    	}
    
    }

    使用总结

    1、参数名获取的本质

    • 所有方案都依赖编译时保留参数名信息(通过-parameters或-g参数), 所以编译的时候有就是有,没有就是没有,尤其是一些第三方依赖的类
    • 如果是第三方依赖的类若未携带调试信息(如未使用上述编译参数进行编译),则无论如何也是无法获取真实参数名, 除非第三方库已使用-parameters编译,仍可通过反射获取(但实际场景较少见)

    2、版本适配建议

    • JDK8+项目: 优先使用-parameters编译参数(性能最佳), 配合StandardReflectionParameterNameDiscoverer
    • JDK7及以下: 使用-g编译参数, 配合LocalVariableTableParameterNameDiscoverer
    • Spring项目: 推荐使用DefaultParameterNameDiscoverer(自动适配最优策略), 可同时配置两种编译参数提升兼容性:

    3、在一些springBoot 2.0以后版本中spring-boot-starter-parent会内置了编译参数配置,所以不用配也可以直接使用ParameterNameDiscoverer, 当然需要确保项目是否覆盖了默认配置导致失效,需要重新配

    知名应用场景举例

    • spring-mvc 的 @RequestParam 参数名的可省略配置
    • mybatis 的 @Param 也可以不用配置

    到此这篇关于Java通过反射获取方法参数名的方式小结的文章就介绍到这了,更多相关Java反射获取方法参数名内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

    您可能感兴趣的文章:
    • java根据方法名称取得反射方法的参数类型示例
    • Java 8新增的方法参数反射实例分析
    • 详解java中反射机制(含数组参数)
    • 论java如何通过反射获得方法真实参数名及扩展研究
    • java反射校验参数是否是基础类型步骤示例
    • java反射获取方法参数名的几种方式总结
    相关栏目:

    用户点评