Original: Taste of Little Sister (wechat official ID: XjjDog), welcome to share, please reserve the source.

By default, we cannot get the parameter names in a method. With reflection, you can only get the order of the arguments and some meaningless variables: arg0, arg1, and so on.

But we do need this information. For example, IDE auto-prompts, documented service interface details, and so on.

This is because the names of these variables are not compiled into the class file at all; they cannot be created out of thin air.

After JDK 8, you can write the method’s parameter name to a class file by specifying the -parameters option at compile time and get that information at run time through reflection.

If your project is a practical Maven build, you can add a few lines of configuration and append parameters.

<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>  
Copy the code

If you use an editor such as IDEA, you can also configure it on the Settings screen. This is not recommended, however, because your configurations are not shareable.

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) {  
        / / parameter name
        System.out.println("\t\t" + parameter.getName());  
        // Whether to declare the parameter name implicitly in the source code
        System.out.println("\t\t\t implicit:" + parameter.isImplicit());  
        // Class file, whether there is a parameter name
        System.out.println("\t\t\t namePresent:" + parameter.isNamePresent());  
        // Whether it is an imaginary parameter
        System.out.println("\t\t\t synthetic:" + parameter.isSynthetic());  
        System.out.println("\t\t\t VarArgs:"+ parameter.isVarArgs()); }}Copy the code

Here are the meanings of several methods:

isImplicit()

If a parameter is implicitly declared in a source file, such as an inner class, the default constructor (with no arguments) actually takes the reference to the main class containing it as the first argument when it is coded to class. This parameter is implicitly declared.

If true, the JDK compiler implicitly generates method parameters in the class file that are not visible in the source file. Normal normal method, this value is false.

isNamePresent()

Whether the parameter name exists in the class file; Depending on whether “-parameter” is specified at compile time, it is usually true for compiled files that specify this parameter. For JDK internal classes, classes compiled by default, this is usually false; You’ll notice that their parameter names are usually ideographic names: arg0, arg1, and so on, which are false.

isSynthetic()

Is a “fictitious” parameter. If true, it indicates that the parameter is neither “explicitly” declared nor implicitly declared in the source file, such as the “values()” and “valueOf(String)” of enum classes, which are “fictitious” system methods by the compiler.

In the Spring environment, it is more convenient because of the support of utility classes.

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); }}}}Copy the code

How do I get it if the Java version is lower than 1.8? We can refer to the Spring LocalVariableTableParameterNameDiscoverer classes.

public String[] getParameterNames(Method method) {
		Method originalMethod = BridgeMethodResolver.findBridgedMethod(method);
		return doGetParameterNames(originalMethod);
}
@Nullable
privateString[] 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);
}
Copy the code

Finally, we come to inspectClass method.

privateMap<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);
			returnmap; }...Copy the code

As you can see, Spring does the parsing in this case by reading the class file directly. We actually get it by reading the data in the LocalVariableTable. If you compile without these debug options, you will also not get the name of the method argument.

To sum up. Before Java8, the Class LocalVariableTable attribute table, need to compile the parameter -g or -g:vars to obtain local variable debugging information; Java8 and later, through the Java. Lang. Reflect the Parameter# getName can obtain, but need to add parameters – the parameters at compile time.

Xjjdog is a public account that doesn’t allow programmers to get sidetracked. Focus on infrastructure and Linux. Ten years architecture, ten billion daily flow, and you discuss the world of high concurrency, give you a different taste. My personal wechat xjjdog0, welcome to add friends, further communication.