原創:小姐姐味道(微信公衆號ID:xjjdog),歡迎分享,轉載請保留出處。java
默認狀況下,咱們是沒法獲取方法中參數名稱的。經過反射機制,也只能獲得參數的順序以及一些沒有意義的變量:arg0
、arg1
等等。程序員
但咱們又確實須要這部分信息。好比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等編輯器,也能夠經過設置界面進行配置。不過不推薦這樣,由於你的這些配置很差進行共享。編輯器
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,歡迎添加好友,進一步交流。