内容简介:推荐阅读:
原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。
默认情况下,我们是无法获取方法中参数名称的。通过反射机制,也只能得到参数的顺序以及一些没有意义的变量: arg0
、 arg1
等等。
但我们又确实需要这部分信息。比如IDE的自动提示,文档化服务接口的详细信息等。
这是因为,这些变量的名字,根本就没有编译进class文件中,它不可能凭空产生。
在JDK 8之后,可以通过在编译时指定 -parameters
选项,将方法的参数名记入class文件,并在运行时通过反射机制获取相关信息。
如果你的项目是实用maven构建,那么就可以加入几行配置,追加参数。
<plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.0</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>utf8</encoding> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin>
如果是用的IDEA等编辑器,也可以通过设置界面进行配置。不过不推荐这样,因为你的这些配置不好进行共享。
在普通 Java 项目里,就可以通过下面的方式来获取反射数据。 Method.getParameters
这个方法是新加的。
public class Test { public static void main(String[] args) throws Exception{ Class clazz = Class.forName("com.test.MethodParameterTest"); Method[] methods = clazz.getMethods(); Constructor[] constructors = clazz.getConstructors(); for (Constructor constructor : constructors) { System.out.println("+++" + constructor.getName()); Parameter[] parameters = constructor.getParameters(); for (Parameter parameter : parameters) { printParameter(parameter); } } System.out.println("------------------"); for (Method method : methods) { System.out.println(method.getName()); Parameter[] parameters = method.getParameters(); for (Parameter parameter : parameters) { printParameter(parameter); } } } private static void printParameter(Parameter parameter) { //参数名 System.out.println("\t\t" + parameter.getName()); //是否在源码中隐式声明的参数名 System.out.println("\t\t\t implicit:" + parameter.isImplicit()); //类文件中,是否存在参数名 System.out.println("\t\t\t namePresent:" + parameter.isNamePresent()); //是否为虚构参数 System.out.println("\t\t\t synthetic:" + parameter.isSynthetic()); System.out.println("\t\t\t VarArgs:" + parameter.isVarArgs()); } }
下面介绍几个方法的意义:
isImplicit()
参数是否为 隐式
声明在源文件中,比如内部类,默认构造函数(无参)其实在编译成class时将会把包含它的主类引用作为首个参数,此参数即为隐式声明。
如果为true,即表示有JDK编译器隐式生成在class文件中的方法参数,而source文件中并不可见。常规的普通方法,此值为false。
isNamePresent()
此参数在class文件中是否有此参数名;受制于在编译时是否指定了“-parameter”,对于指定此参数的编译文件,通常为true;对于JDK 内部类、默认编译的类,通常为false;此时你会发现,它们的参数名通常为表意名称:arg0、arg1等等,此时为false。
isSynthetic()
是否为“虚构”参数,如果为true,表示既不是“显式”声明、也不是隐式声明在源文件中的参数,比如enum类的“values()”、“valueOf(String)”这是编译器“虚构”的系统方法。
在Spring环境中,由于有 工具 类的支持,会更加方便一些。
public class SpringTest { private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); public static void main(String[] args) throws Exception{ Class clazz = Class.forName("com.test.MethodParameterTest"); Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method.getName()); //JDK 1.8 + is better. String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); if (parameterNames == null) { continue; } for (String pn : parameterNames) { System.out.println("\t\t" + pn); } } } }
那Java版本低于1.8的时候,又是怎么获取的呢?我们可以参考Spring的 LocalVariableTableParameterNameDiscoverer
类。
public String[] getParameterNames(Method method) { Method originalMethod = BridgeMethodResolver.findBridgedMethod(method); return doGetParameterNames(originalMethod); } @Nullable private String[] doGetParameterNames(Executable executable) { Class<?> declaringClass = executable.getDeclaringClass(); Map<Executable, String[]> map = this.parameterNamesCache.computeIfAbsent(declaringClass, this::inspectClass); return (map != NO_DEBUG_INFO_MAP ? map.get(executable) : null); }
最后就走到了 inspectClass
方法中。
private Map<Executable, String[]> inspectClass(Class<?> clazz) { InputStream is = clazz.getResourceAsStream(ClassUtils.getClassFileName(clazz)); if (is == null) { // We couldn't load the class file, which is not fatal as it // simply means this method of discovering parameter names won't work. if (logger.isDebugEnabled()) { logger.debug("Cannot find '.class' file for class [" + clazz + "] - unable to determine constructor/method parameter names"); } return NO_DEBUG_INFO_MAP; } try { ClassReader classReader = new ClassReader(is); Map<Executable, String[]> map = new ConcurrentHashMap<>(32); classReader.accept(new ParameterNameDiscoveringVisitor(clazz, map), 0); return map; } ...
可以看到,这种情况下,Spring是通过直接读取class文件进行解析的。实际上是通过读取 LocalVariableTable
中的数据进行获取的。如果你编译的时候没有加入这些debug选项,同样也拿不到方法参数的具体名称。
总结一下。Java8以前,读取Class中的 LocalVariableTable
属性表,需要编译时加入参数 -g
或者 -g:vars
获取方法局部变量调试信息;Java8及其以后,通过 java.lang.reflect.Parameter#getName
即可获取,但需要编译时加入参数 -parameters
参数。
作者简介: 小姐姐味道 (xjjdog),一个不允许 程序员 走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。
后台回复“ 加群 ”,带你进入高手如云交流群
推荐阅读:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 根据 PID 获取 K8S Pod名称 - 反之 POD名称 获取 PID
- 根据 PID 获取容器所在的 Pod 名称
- 使用php获取多个复选框名称/ id
- C# 获取 Windows 操作系统版本和名称
- 什么是SPARQL查询以获取我的triplestore中存在的所有图的名称?
- 设定新的进程名称
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。