原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

默认情况下,我们是无法获取方法中参数名称的。通过反射机制,也只能得到参数的顺序以及一些没有意义的变量:arg0arg1等等。

但我们又确实需要这部分信息。比如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);}@Nullableprivate 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 {Cla***eader cla***eader = new Cla***eader(is);Map<Executable, String[]> map = new ConcurrentHashMap<>(32);cla***eader.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,欢迎添加好友,进一步交流。

©著作权归作者所有:来自51CTO博客作者wx5ce7be7e7e4cc的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. RTX3080Ti和RTX2080Ti性能差距 RTX3080Ti和RTX2080Ti参数对比哪
  2. crmeb pro单商户前台uniapp修改编译打包教程
  3. crmeb pro单商户前端uniapp编译打包公众号h5教程
  4. rtx3090和rtx3080ti性能差距 rtx3090和rtx3080ti 参数对比哪个
  5. rtx3080和rtx3080ti性能差距 rtx3080和rtx3080ti 参数对比哪个好
  6. 【DB笔试面试660】在Oracle中,在编译存储过程、函数等对象时无响
  7. Shell脚本中的while getopts用法小结
  8. Linux运维实战——MySQL源码包个性化部署【CentOS】
  9. FFmpeg编译4.1.4并移植到Android

随机推荐

  1. 读书笔记-Linux C 编程从基础到实践-第一
  2. 用GCC编译链接程序--编译链接器GCC常用功
  3. 嵌入式Linux文件系统及其存储机制分析
  4. Linux系列:linux学习之路(入门类、编程类、
  5. Oracle表按字段和|分格符导出文件
  6. 《乐者为王——自由软件Linux之父李纳斯&
  7. Shell脚本创建linux用户帐户但密码出错
  8. linux tee 命令详解
  9. 黑客常用 Linux 入侵常用命令
  10. CentOS 6.5安装配置LNMP服务器(Nginx+PHP+