小张教你如何:Java如何获取方法参数具体名称

38 篇文章 0 订阅

默认情况下,我们是无法获取方法中参数名称的。通过反射机制,也只能得到参数的顺序以及一些没有意义的变量: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等编辑器,也可以通过设置界面进行配置。不过不推荐这样,因为你的这些配置不好进行共享。

-parameters

在这里插入图片描述

在普通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());
    }
    
}

下面介绍几个方法的意义:

  1. isImplicit()
    参数是否为隐式声明在源文件中,比如内部类,默认构造函数(无参)其实在编译成class时将会把包含它的主类引用作为首个参数,此参数即为隐式声明。
    如果为true,即表示有JDK编译器隐式生成在class文件中的方法参数,而source文件中并不可见。常规的普通方法,此值为false。
  2. isNamePresent()
    此参数在class文件中是否有此参数名;受制于在编译时是否指定了“-parameter”,对于指定此参数的编译文件,通常为true;对于JDK 内部类、默认编译的类,通常为false;此时你会发现,它们的参数名通常为表意名称:arg0、arg1等等,此时为false。
  3. 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参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值