原理

编译之后的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. Set Matrix Zeroes leetcode java

    题目: Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. cl ...

  2. 别再为了this发愁了:JS中的this机制

    题记:JavaScript中有很多令人困惑的地方,或者叫做机制.但是,就是这些东西让JavaScript显得那么美好而与众不同.比方说函数也是对象.闭包.原型链继承等等,而这其中就包括颇让人费解的th ...

  3. 转:[大数据竞赛]协同过滤在这个问题上是否work

    http://bbs.aliyun.com/read/154433.html?spm=5176.7189909.0.0.gzyohy&fpage=2 看到主办方之前发的一篇文章里提到,这个购买 ...

  4. redis 基本性能测试说明

    http://ghoulich.xninja.org/2016/11/17/how-to-use-redis-benchmark-to-measure-performance/

  5. (算法)Travel Information Center

    题目: Aps Island has many cities. In the summer, many travellers will come to the island and attend fe ...

  6. Oracle——数据库启动与关闭

    本文内容 服务器环境 客户端环境 概述 启动数据库 关闭数据库 补充 参考资料 本文说明 Oracle 数据库的启动和关闭,内容虽然基础,但是在数据库很多操作中都需要,因此,基础而重要,必须深入理解. ...

  7. performSelector 多个参数

    [self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first& ...

  8. iOS开发 - Content hugging priority & Content compression resistance priority

    1. 什么是Content hugging priority 你可以把它想象成一根放在视图上的橡皮筋. 这根橡皮筋会组织视图超过它本身的固有大小(intrinsic content size). 它存 ...

  9. javascript 替换 window.onload 的 document.ready

    通常我们想要在页面内容加载完成后运行 JS 时,都会使用 window.onload 来处理,比如: window.onload = function(){      alert('Hello Wor ...

  10. Plupload上传插件中文帮助文档

    Plupload上传插件中文帮助文档 配置参数 实例化一个plupload对象时,也就是 new plupload.Uploader(),需要传入一个对象作为配置参数.后面内容中出现的plupload ...