获取Java方法参数名的原理与实践

作者 : 码海拾光 本文共5391个字,预计阅读时间需要14分钟 发布时间: 2025-03-24 共52人阅读




获取Java方法参数名的原理与实践

前言

你是否曾经好奇过 SpringMVC 是如何获取方法参数名实现请求参数映射的呢?是反射还是字节码技术?最近群友在知乎回答了该问题,苦于没有博客,特此转载分享 Java 中获取方法参数名的原理。

ps. 该群友单身优质男青年,95后,在线找女票ing,有意者mm

原文出处:https://zhuanlan.zhihu.com/p/610288146

作者:xinxi

javac 命令

Java里面获取方法的参数名大概有两种方法,对应的javac的两个选项如下

Image 1: Image

-g 选项

生成调试用的东西,它有三个,lines、vars、source,也就是调试的时候用的行号、参数名和源文件。直接使用 -g 的话会把这三个信息都生成。

编译时使用 -g 选项,然后使用 javap 可以看到会有一个 LocalVariableTable 块,里面有方法的参数的名字,如下图所示

Image 2: Image

-parameters 选项

直接看效果吧,它有个 MethodParameters 块,如下图

Image 3: Image

使用代码获取 LocalVariableTable 块

我们自己去读取 class 文件貌似有点难度,借助一些处理字节码的框架会比较ok

使用ASM

import org.springframework.asm.*;import static org.springframework.asm.Opcodes.*;

public class Main {  
    public static void main(String[] args) throws Exception {  
        ClassPrinter cp = new ClassPrinter();  
        ClassReader cr = new ClassReader("com.example.test.Dog");  
        cr.accept(cp, 0);  
    }  
}

class ClassPrinter extends ClassVisitor {  
    public ClassPrinter() {  
        super(ASM9);  
    }  
    @Override  
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {  
        System.out.println("---------------------------------------");  
        System.out.println(name + " | " + desc);  
        return new MethodVisitor(ASM9) {  
            @Override  
            public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {  
                System.out.println(name + " \t " + descriptor);  
                super.visitLocalVariable(name, descriptor, signature, start, end, index);  
            }  
        };  
    }  
}

这里用的是 Spring ASM,与原生的差不太多,运行效果如下

Image 4: Image

使用javassist

import javassist.ClassPool;  
import javassist.CtClass;  
import javassist.CtMethod;  
import javassist.bytecode.CodeAttribute;  
import javassist.bytecode.LocalVariableAttribute;  
import javassist.bytecode.MethodInfo;import java.lang.reflect.Modifier;

public class Main {  
    public static void main(String[] args) throws Exception {  
        ClassPool pool = ClassPool.getDefault();  
        CtClass ctClass = pool.get("com.example.test.Dog");  
        CtMethod ctMethod = ctClass.getDeclaredMethod("func");  
        MethodInfo methodInfo = ctMethod.getMethodInfo();  
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();  
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);  
        if (attr != null) {  
            int len = ctMethod.getParameterTypes().length;  
            int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;  
            for (int i = 0; i < len; i++) {  
                System.out.print(attr.variableName(i + pos) + ' ');  
            }  
            System.out.println();  
        }  
    }  
}

运行效果如下

Image 5: Image

使用代码获取 MethodParameters 块

import java.lang.reflect.Method;  
import java.lang.reflect.Parameter;public class Main {  
    public static void main(String[] args) throws Exception {  
        Class<?> clazz = Dog.class;  
        Method method = clazz.getDeclaredMethod("func", String.class, Integer.class);  
        Parameter[] parameters = method.getParameters();  
        for (final Parameter parameter : parameters) {  
            if (parameter.isNamePresent()) {  
                System.out.print(parameter.getName() + ' ');  
            }  
        }  
    }  
}

运行效果如下

Image 6: Image

构建工具

写Java的应该很少有手动 javac 的吧?所以看看构建工具是很有必要的。

Maven

Maven编译代码使用的是 maven-compiler-plugin 插件,看看它是怎么玩的

amaven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html

Image 7: Image

Image 8: Image

可以看到 debug 默认 true,parameters 默认 false。

Gradle

参考

adocs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html

Image 9: Image

可以看到 debug 默认是 true。

没找到 parameters…..你可以自己指定这个选项,默认应该是没有开启这个。

SpringBoot 项目

  • Maven

如果你是下面这样写的话

<parent>  
      <groupId>org.springframework.boot</groupId>  
      <artifactId>spring-boot-starter-parent</artifactId>  
      <version>2.7.7</version>  
      <relativePath/> <!-- lookup parent from repository -->  
</parent>  

那么可以看到编译插件被动了点手脚,如下

Image 10: Image

还记得么,Maven的编译插件 debug 默认 true,parameters 默认 false,而SpringBoot把parameters也打开了。

  • Gradle

我们直接查看SpringBoot的Gradle插件源码如下

Image 11: Image

还记得么, Gradle编译时debug 默认是 true,parameters 默认 false。而SpringBoot插件会检查如果没有 -parameters 的话,就加上去。

Spring 框架

spring-core 模块中有个 ParameterNameDiscoverer 接口,专门用来获取参数的名字。比较重要的实现是如下两个

  • StandardReflectionParameterNameDiscoverer 类

使用JDK 8的反射设施来反省参数名称(编译时需指定 -parameters 参数)

  • LocalVariableTableParameterNameDiscoverer 类

使用 ASM 库来分析类文件,使用方法属性中的 LocalVariableTable 信息来发现参数名称(编译时需指定 -g 参数生成调试信息)

但是实际上使用的类是 DefaultParameterNameDiscoverer,源代码如下

Image 12: Image

一看就应该知道是怎么工作的,把能用的手段都用上对吧。

测试代码如下

import com.google.common.collect.Lists;  
import org.springframework.core.DefaultParameterNameDiscoverer;  
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;  
import org.springframework.core.StandardReflectionParameterNameDiscoverer;public class Main {  
    public static void main(String[] args) {  
        Lists.newArrayList(  
                new LocalVariableTableParameterNameDiscoverer(),  
                new StandardReflectionParameterNameDiscoverer(),  
                new DefaultParameterNameDiscoverer()  
        ).forEach(parameterNameDiscoverer -> {  
            try {  
                String[] parameterNames = parameterNameDiscoverer  
                        .getParameterNames(Dog.class.getDeclaredMethod("func", String.class, Integer.class));  
                for (String parameterName : parameterNames) {  
                    System.out.print(parameterName + ' ');  
                }  
                System.out.println();  
            } catch (NoSuchMethodException e) {  
                throw new RuntimeException(e);  
            }  
        });  
    }  
}

运行效果如下

Image 13: Image

既没有-g也没有-parameters还能抢救一下吗?

能的。用注解,不过这已经不算是获取方法的参数名了,但也能用不是。。。

Image 14: Image





发表回复