原理

编译之后的class文件默认是不带有参数名称信息的,使用 IDE 时,反编译jar包得到的源代码函数参数名称是 arg0,arg1......这种形式,这是因为编译 jar 包的时候没有把符号表编译进去。

JDK1.7 及以下版本的 API 并不能获取到函数的参数名称,需要使用字节码处理框架,如 ASM、javassist 等来实现,且需要编译器开启输出调试符号信息的参数的-g。这个过程简单描述就是:

  • 编译器javac使用-g输出调试符号信息到class文件
  • 程序通过字节码解析框架解析class文件获取函数参数名称

显然,通过字节码框架的方式需要读文件,不够优雅。jdk8提供了反射机制直接获取函数参数名称,即在javac命令上加上 -parameter参数,这个参数默认是关闭的。

著名的 ORM 框架 Mybatis 就使用了函数参数注解的方式来实现,使用函数参数注解的方法具有如下优点:

  • 框架实现简单
  • 可以灵活设置参数,在注解中绑定的是字符串即可,无需是合法的标识符。

使用 ASM

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type; public class TestMain { public static void main(String[] args) { Class<?> clazz = TestMain.class;
try {
Method method = clazz.getDeclaredMethod("test", String.class,
int.class);
String[] pns = getParameterNamesByAsm5(clazz, method);
System.out.print(method.getName() + " : ");
for (String parameterName : pns) {
System.out.print(parameterName + ' ');
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
} public static void test(String param1, int param2) {
System.out.println(param1 + param2);
} public static String[] getParameterNamesByAsm5(Class<?> clazz,
final Method method) {
final Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes == null || parameterTypes.length == 0) {
return null;
}
final Type[] types = new Type[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
types[i] = Type.getType(parameterTypes[i]);
}
final String[] parameterNames = new String[parameterTypes.length]; String className = clazz.getName();
int lastDotIndex = className.lastIndexOf(".");
className = className.substring(lastDotIndex + 1) + ".class";
InputStream is = clazz.getResourceAsStream(className);
try {
ClassReader classReader = new ClassReader(is);
classReader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
// 只处理指定的方法
Type[] argumentTypes = Type.getArgumentTypes(desc);
if (!method.getName().equals(name)
|| !Arrays.equals(argumentTypes, types)) {
return super.visitMethod(access, name, desc, signature,
exceptions);
}
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLocalVariable(String name, String desc,
String signature, org.objectweb.asm.Label start,
org.objectweb.asm.Label end, int index) {
// 非静态成员方法的第一个参数是this
if (Modifier.isStatic(method.getModifiers())) {
parameterNames[index] = name;
} else if (index > 0) {
parameterNames[index - 1] = name;
}
}
};
}
}, 0);
} catch (IOException e) {
} finally {
try {
if (is != null) {
is.close();
}
} catch (Exception e2) {
}
}
return parameterNames;
} }

使用 javaassist

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo; public class TestMain { public static void main(String[] args) {
Class<?> clazz = TestMain.class;
ClassPool pool = ClassPool.getDefault();
try {
CtClass ctClass = pool.get(clazz.getName());
CtMethod ctMethod = ctClass.getDeclaredMethod("test"); // 使用javassist的反射方法的参数名
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
.getAttribute(LocalVariableAttribute.tag);
if (attr != null) {
int len = ctMethod.getParameterTypes().length;
// 非静态的成员函数的第一个参数是this
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
System.out.print("test : ");
for (int i = 0; i < len; i++) {
System.out.print(attr.variableName(i + pos) + ' ');
}
System.out.println();
}
} catch (NotFoundException e) {
e.printStackTrace();
}
} public static void test(String param1, int param2) {
System.out.println(param1 + param2);
}
}

Spring 对 ASM 的封装

spring-core 中的 LocalVariableTableParameterNameDiscoverer 它对 ASM 进行了封装。

import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer; public class TestMain { public static void main(String[] args) {
ParameterNameDiscoverer parameterNameDiscoverer =
new LocalVariableTableParameterNameDiscoverer();
try {
String[] parameterNames = parameterNameDiscoverer
.getParameterNames(TestMain.class.getDeclaredMethod("test",
String.class, int.class));
System.out.print("test : ");
for (String parameterName : parameterNames) {
System.out.print(parameterName + ' ');
}
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
} public static void test(String param1, int param2) {
System.out.println(param1 + param2);
}
}

jdk8中自带函数参数功能

在Java1.8之后,可以通过反射API java.lang.reflect.Executable.getParameters来获取到方法参数的元信息,这要求在使用编译器时加上-parameters参数(javac默认不带此参数),它会在生成的.class文件中额外存储参数的元信息,这会增加class文件的大小。

import java.lang.reflect.Method;
import java.lang.reflect.Parameter; public class TestMain { public static void main(final String[] arguments) throws Exception {
Class<?> clazz = TestMain.class;
Method method = clazz.getDeclaredMethod("test", String.class, int.class);
System.out.print("test : ");
Parameter[] parameters = method.getParameters();
for (final Parameter parameter : parameters) {
if (parameter.isNamePresent()) {
System.out.print(parameter.getName() + ' ');
}
}
} public void test(String param1, int param2) {
System.out.println(param1 + param2);
}
}

使用注解

上面介绍的几种方法都需要依赖编译器附加一定的编译参数,才能获取到。如果程序编译不想保留这些调试信息和附加的元数据,或者你开发一个了框架提供给别人使用,可是该框架想要获取用户代码的函数参数名,因为你并不能控制别人怎么编译Java代码,这时怎么提供一种不受编译器影响的途径来确保获取到函数参数名呐?看看spring mvc是怎么做的——使用函数参数注解。

定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Parameter {
String value();
}

使用注解的例子

import java.lang.annotation.Annotation;
import java.lang.reflect.Method; public class TestMain { public static void main(String[] args) throws Exception {
Method method = TestMain.class.getMethod("test", String.class, int.class);
System.out.print("test : ");
Annotation parameterAnnotations[][] = method.getParameterAnnotations();
for (int i = 0; i < parameterAnnotations.length; i++) {
for (Annotation annotation : parameterAnnotations[i]) {
if (Parameter.class.equals(annotation.annotationType())) {
System.out.print(((Parameter) annotation).value() + ' ');
}
}
}
} public void test(@Parameter("param1") String param1,
@Parameter("param2") int param2) {
System.out.println(param1 + param2);
}
}

参考资料 一篇文章详细解读 SpringMVC 的函数参数绑定过程

Java获取函数参数名称的几种方法:https://blog.csdn.net/wwwwenl/article/details/53427039

https://www.cnblogs.com/guangshan/p/4660564.html

廖雪峰:Java8 中获取函数参数名称

Java获取函数参数名称的更多相关文章

  1. php -- 获取函数参数

    ----- 015-parameter.php ----- <!DOCTYPE html> <html> <head> <meta http-equiv=&q ...

  2. java获取本机名称、IP、MAC地址和网卡名称

    java获取本机名称.IP.MAC地址和网卡名称 摘自:https://blog.csdn.net/Dai_Haijiao/article/details/80364370 2018年05月18日 1 ...

  3. Java 函数参数传递方式详解 分类: Java Game 2014-08-15 06:34 82人阅读 评论(0) 收藏

    转:http://zzproc.iteye.com/blog/1328591 在阅读本文之前,根据自己的经验和理解,大家可以先思考并选择一下Java函数的参数传递方式:  A. 是按值传递的?  B. ...

  4. Java 8——保存参数名称

    一.详述 在很多情况下,程序需要保存方法参数名称,如Mybatis中的mapper和xml中sql的参数绑定.但是在java 8之前的编译器是不支持保存方法参数名至class文件中的. 所以很多框架都 ...

  5. Java获取方法参数名、Spring SpEL解析

    @Test public void testParse() { //表达式解析 ExpressionParser expressionParser = new SpelExpressionParser ...

  6. python基础之函数参数,名称空间,以及函数嵌套

    函数进阶内容梗概: 1. 函数参数--动态传参 2. 名称空间, 局部名称空间, 全局名称空间, 作⽤用域, 加载顺序. 3. 函数的嵌套 4. gloabal , nonlocal 关键字 1. 函 ...

  7. Summary: Java中函数参数的传递

    函数调用参数传递类型(java)的用法介绍. java方法中传值和传引用的问题是个基本问题,但是也有很多人一时弄不清. (一)基本数据类型:传值,方法不会改变实参的值. public class Te ...

  8. php动态获取函数参数

    PHP 在用户自定义函数中支持可变数量的参数列表.其实很简单,只需使用 func_num_args() , func_get_arg() ,和 func_get_args()  函数即可. 可变参数并 ...

  9. [ActionScript 3.0] AS3 获取函数参数个数

    function createFunction(param1:String,param2:String,param3:int=0):void { trace(arguments.length);//a ...

随机推荐

  1. 异常捕获 崩溃 Bugly ACRC 简介 总结 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. 【Kafka】Kafka-配置参数详解-参数调优

    Kafka-配置参数详解-参数调优 kafka 目录_百度搜索 为什么kafka使用磁盘而不是内存 - CSDN博客 Kafka 配置说明 - 風吹云动 - 博客园 kafka生产服务器配置 - Or ...

  3. pycharm+python+Django之web开发环境的搭建(windows)

    转载:https://blog.csdn.net/yjx2323999451/article/details/53200243/ pycharm+python+Django之web开发环境的搭建(wi ...

  4. C#.NET常见问题(FAQ)-如何把定义存放类实例的数组

    数组存放的可以是普通的int,double,string类型,也可以是自定义的类的实例   如果数组长度未知,可以用list对象存放   更多教学视频和资料下载,欢迎关注以下信息: 我的优酷空间: h ...

  5. android中使用Nine-Patch图片

    android中可以把图片进行处理,如果图片被拉伸的话,允许让图片部分区域不拉伸,部分区域拉伸.这个功能非常好,比如聊天的气泡,如果整个气泡被拉伸的话,会非常的丑. 老版的sdk中提供的有draw9p ...

  6. 【树莓派】树莓派刷Android系统

    树莓派3安装Android TV系统图文教程 http://www.mz6.net/news/android/6866.html 树莓派3 Android TV系统怎样安装?树莓派3一个重要用途就是当 ...

  7. 关于RF对于不在屏幕内的页面元素的处理办法

    1.碰到的问题: 最近在公司用Robot framework+Selenium2Library做项目,碰到部分页面比较长,无法完全显示在屏幕内,需要上下滚动滚动条才能看到下半部分的页面元素.于是呼,问 ...

  8. 微软BI 之SSAS 系列 - 在 SQL Server 2012 下查看 SSAS 分析服务的模型以及几个模型的简单介绍

    在SSDT中部署一个 SSAS 项目到本地服务器上出现错误. You cannot deploy the model because the localhost deployment server i ...

  9. 使用Spring框架入门三:基于XML配置的AOP的使用

    一.引入Jar包 <!--测试1使用--> <dependency> <groupId>org.springframework</groupId> &l ...

  10. Linux中最常用的JAVA_HOME配置

    一.配置 更改下面配置中的JAVA_HOME路径为你的路径. export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.144-0.b01.el7_ ...