關於題目
首先解釋一下題目. 我們知道, Java通過反射,可以從一個類得知它有哪些方法,有哪些變量,也可以知道每個方法中有哪幾個什么類型的傳入參數。但有一個東西反射取不到,那就是我們對方法傳入參數的命名。
取得傳入參數的名字有什么意義?
對這個問題的探究,源於在寫一個測試類時候的需求。假設我們有一個類需要測試,這個類中有數十個方法。為每個方法編寫測試類,將耗費大量的時間和精力。因此我有一種想法,就是通過java的反射,獲得這個類所有的方法,再通過傳入參數的名字和參數類型,來生成一些符合要求的數據進行傳入。(能這樣生成數據的前提是:這個類的編碼需要遵循嚴格的規范,對參數的命名有統一的標准,同時,這個類應該和某種業務緊密相關,這樣,才能通過業務和參數名字,判斷應生成什么合適的數據)。如果能做到上面說的,那么對具有數十或數百個方法的類,要測試的話只需要傳入這個類就可以了。
存在的問題
根據上面的設想,問題就出現了。獲得類的方法,獲得類的參數類型,反射都可以做到。但參數名稱呢?上網求證,多數人給了直接否定的答案。因為API中根本沒有提供相關的方法。但有一些人的觀點啟發了我。他們提到,IDE(如eclipse,myeclipse)中在編碼過程中,調用一個類的方法,在代碼提示的時候,ide是可以顯示出方法中的參數名字的,如下圖:

IDE是怎樣做到的呢,如果IDE可以做到,我們是否可以嘗試去分析它們的做法,來獲得參數名稱。
可能的做法
網上找到了一個很直觀的方法——通過直接讀取.java文件,把類作為一個普通文本,用正則表達式匹配方法,來直接獲取參數的名字。
- /**
- * @author zhangc
- *
- *一個測試程序,用來掃描文件(java文件),找出所有方法的參數列表
- */
- import java.io.*;
- import java.util.regex.*;
- public class ScanSource {
- static void findArgsList(Object targetSrc) {
- /*
- * 正則匹配串基本上是這樣子分組情況(A(B(c(d))))
- * 串是:(\\w+\\s+\\w+\\s*\\(((\\s*\\w+\\s*(\\[
- * \\])*\\s*\\s+(\\[\\])*\\s*\\w+\\s*(\\[\\])*,?)+)\\)\\s*\\{) 比如public
- * static void findArgsList(Object targetSrc,int []a){
- * A是匹配整個方法定義行:這里是:static void findArgsList(Object targetSrc,int []a){
- * B是匹配匹配參數列表:這里是Object targetSrc,int []a
- * C是匹配一個參數,包括類型和類型名稱和逗號:這里是Object targetSrc, D是匹配數組標識符:這里是[]
- * 這個串有點bt,水平有限,只能這樣
- */
- Pattern p = Pattern
- .compile("(\\w+\\s+\\w+\\s*\\(((\\s*\\w+\\s*(\\[\\])*\\s*\\s+(\\[\\])*\\s*\\w+\\s*(\\[\\])*,?)+)\\)\\s*\\{)");
- Matcher m = p.matcher((CharSequence) targetSrc);
- // locate the all methord defination
- while (m.find()) {
- String methodName = m.group(0);
- String methodArgName = m.group(1);
- String strArgs = m.group(2);
- String fourArgs = m.group(3);
- System.out.println(methodName + "\n" + methodArgName + "\n" + strArgs + "\n" + fourArgs + "\n");
- }
- }
- public static String LoadTargetFile(String targetFileName) {
- String result = null;
- try {
- FileInputStream fis = new FileInputStream(targetFileName);
- // 臨時分配10000size給byte數組。
- byte[] bufReceived = new byte[10000];
- int counts = fis.read(bufReceived);
- byte[] bufActual = new byte[counts];
- System.arraycopy(bufReceived, 0, bufActual, 0, counts);
- result = new String(bufActual);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return result;
- }
- public static void main(String[] args) {
- String target = LoadTargetFile("src/com/spring/aop/TestAspect.java");
- System.out.println(target);
- findArgsList(target);
- }
- }
這個通過正則表達式的類,在我寫的一個簡單的測試類中,是可以取得參數的值的,但當把它用在我們那個有幾十個方法的類的時候,表達式的匹配就失效了,沒有得到任何的結果(具體原因可能是正則表達式的錯誤,沒能匹配到一些方法)。同時,這種方法需要有.java這個源文件,而在IDE中引入的常常是.class組成的Jar包。為了進一步了解IDE對方法傳入參數名的處理,下面我做了一個測試。
測試IDE對方法傳入參數的處理
建立一個工程。在工程中新建如下的一個類:
- package testplugin;
- public class TestJar {
- public void testJar(String jarName, String yourName){
- System.out.println("jarName:" + jarName + "|| yourName:" + yourName);
- }
- }
接着我們用2種方式對這個類打jar包:
1. 用javac編譯類文件然后打到jar包中,命名為testPlugin_javac.jar.
2. 用MyEclipse直接對工程進行導出,導出為testPlugin_myeclipse.jar.
(打開2個jar中的TestJar.class文件,會發現2個class文件有差異)。
再建立一個工程,先后將2個jar包引入做實驗,可以看到:
1. 引入testPlugin_javac.jar, 調用testJar方法,如下圖

可以看到,2個傳入參數失去了原有的名稱。
2. 移除上面的包,引入testPlugin_myEclipse.jar, 調用testJar方法,如下圖

可以看到,參數名稱被識別出來了。
關鍵在於,2個jar包中的class文件不同。我們打開2個class文件(我們只是直觀的看一下class文件中的變量,所以沒有用專用的工具查看):
javac生成的.class:

myelipse直接打出來的.class(實際上就是調用了debug模式編譯出來的.class):

2個class文件里下面的部分都有SourceFile塊。應該是用來表示這個class文件是從哪個java文件編譯來的。我們重點看上面的部分。
可以看到,用普通的javac編譯出來的類,方法的傳入參數名會被編譯器修改,於是上面第一個圖里SourceFile以上的部分就找不到jarName和yourName 2個名字。而只有通過-debug模式編譯出來的類,它的參數名才能被保存下來。而也就是在.class文件中有保留下來參數名的jar包,在IDE中代碼提示才能正確顯示出參數名字。
那么說明IDE是否能識別類中的方法名取決於編譯過后產生的不同的class文件。那么下一節我們會使用工具來解析這2個class文件來看其中到底有什么不同。

