Java如何獲取方法參數具體名稱?這是個好問題!

原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java

默認狀況下,咱們是沒法獲取方法中參數名稱的。經過反射機制,也只能獲得參數的順序以及一些沒有意義的變量:arg0arg1等等。程序員

但咱們又確實須要這部分信息。好比IDE的自動提示,文檔化服務接口的詳細信息等。微信

這是由於,這些變量的名字,根本就沒有編譯進class文件中,它不可能憑空產生。架構

在JDK 8以後,能夠經過在編譯時指定-parameters選項,將方法的參數名記入class文件,並在運行時經過反射機制獲取相關信息。併發

若是你的項目是實用maven構建,那麼就能夠加入幾行配置,追加參數。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,歡迎添加好友,​進一步交流。​

相關文章
相關標籤/搜索