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

前言

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

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

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

作者:xinxi

javac 命令

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


-g 选项

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

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


-parameters 选项

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


使用代码获取 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 + " " + descriptor);
super.visitLocalVariable(name, descriptor, signature, start, end, index);
}
};
}
}

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


使用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 != ) {
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();
}
}
}

运行效果如下


使用代码获取 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() + ' ');
}
}
}
}

运行效果如下


构建工具

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

Maven

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

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



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

Gradle

参考

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


可以看到 debug 默认是 true。

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

SpringBoot 项目

  • Maven

如果你是下面这样写的话

<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.7.7version>
<relativePath/>
parent>

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


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

  • Gradle

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


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

Spring 框架

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

  • StandardReflectionParameterNameDiscoverer 类

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

  • LocalVariableTableParameterNameDiscoverer 类

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

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


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

测试代码如下

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);
}
});
}
}

运行效果如下


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

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


展开阅读全文

页面更新:2024-04-10

标签:行号   在线   参数   方法   源文件   注解   能用   字节   选项   原理   效果

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top