優先級問題是扁平路徑聲明方法與生俱來固有的問題,但它不是隻有Java的classpath纔有的問題。要解決這個問題,你只需站到富有傳奇色彩的軟件巨構的肩膀上:Unix操作系統有一個which命令,在命令參數中指定一個名字,which就會顯示出當這個名字作爲命令執行時執行文件的路徑名。實際上,which命令是分析PATH變量,然後找出命令第一次出現的位置。對於Java的類路徑管理來說,這應該也是一個好工具。在它的啓發之下,我着手設計了一個Java工具JWhich。這個工具要求指定一個Java類的名字,然後根據classpath的指引,找出類裝載器即將裝載的類所在位置的絕對路徑。
下面是一個JWhich的使用實例。它顯示出當Java類裝載器裝載com.clarkware.ejb.ShoppingCartBean類時,該類第一次出現位置的絕對路徑名,查找結果顯示該類在某個目錄下:
> java JWhich com.clarkware.ejb.ShoppingCartBean
Class 'com.clarkware.ejb.ShoppingCartBean' found in '/home/mclark/classes/com/clarkware/ejb/ShoppingCartBean.class' |
下面是第二個JWhich的使用實例。它顯示出當Java類裝載器裝載javax.servlet.http.HttpServlet類時,該類第一次出現位置的絕對路徑名,查找結果顯示該類在某個檔案文件中:
> java JWhich javax.servlet.http.HttpServlet
Class 'javax.servlet.http.HttpServlet' found in 'file:/home/mclark/lib/servlet.jar!/javax/servlet/http/HttpServlet.class' |
四、JWhich的工作過程
要精確地測定classpath中哪一個類先被裝載,你必須深入到類裝載器的思考方法。事實上,具體實現的時候並沒有聽起來這麼複雜——你只需直接詢問類裝載器就可以了!
1: public class JWhich {
2: 3: /** 4: * 根據當前的classpath設置, 5: * 顯示出包含指定類的類文件所在 6: * 位置的絕對路徑 7: * 8: * @param className <類的名字> 9: */ 10: public static void which(String className) { 11: 12: if (!className.startsWith("/")) { 13: className = "/" + className; 14: } 15: className = className.replace('.', '/'); 16: className = className + ".class"; 17: 18: java.net.URL classUrl = 19: new JWhich().getClass().getResource(className); 20: 21: if (classUrl != null) { 22: System.out.println("/nClass '" + className + 23: "' found in /n'" + classUrl.getFile() + "'"); 24: } else { 25: System.out.println("/nClass '" + className + 26: "' not found in /n'" + 27: System.getProperty("java.class.path") + "'"); 28: } 29: } 30: 31: public static void main(String args[]) { 32: if (args.length > 0) { 33: JWhich.which(args[0]); 34: } else { 35: System.err.println("Usage: java JWhich "); 36: } 37: } 38: } |
首先,你必須稍微調整一下類的名字以便類裝載器能夠接受(12-16行)。在類的名字前面加上一個“/”表示要求類裝載器對classpath中的類名字進行逐字精確匹配,而不是嘗試隱含地加上調用類的包名字前綴。把所有“.”轉換爲“/”的目的是,按照類裝載器的要求,把類名字格式化成一個合法的URL資源名。
接下來,程序向類裝載器查詢資源,這個資源的名字必須和經過適當格式化的類名字匹配(18-19行)。每一個Class對象維護着一個對裝載它的ClassLoader對象的引用,所以這裏是向裝載JWhich類的類裝載器查詢。Class.getResource()方法實際上委託裝入該類的類裝載器,返回一個用於讀取類文件資源的URL;或者,當指定的類名字不能在當前的classpath中找到時,Class.getResource()方法返回null。
最後,如果當前的classpath中能夠找到指定的類,則程序顯示包含該類的類文件所在位置的絕對路徑名(21-24行)。作爲一種調試輔助手段,如果當前classpath中不能找到指定的類,則程序獲取java.class.path系統屬性並顯示當前的classpath(24-28行)。
很容易想象,在使用Servlet引擎classpath的Java Servlet中,或者在使用EJB服務器classpath的EJB組件中,上面這段簡單的代碼是如何運作。例如,如果JWhich類是由Servlet引擎的定製類裝載器裝入,那麼程序將用Servlet引擎的類裝載器去尋找指定的類。如果Servlet引擎的類裝載器不能找到類文件,它將委託它的父類裝載器。一般地,當JWhich被某個類裝載器裝入時,它能夠找出當前類裝載器以及所有其父類裝載器所裝入的所有類。
【結束語】
如果需要是所有發明之母,那麼幫助我們管理Java類路徑的工具可以說遲到了很長時間。Java新聞組和郵件列表中充塞着許多有關classpath的問題,現在JWhich爲我們提供了一個簡單卻強大的工具,幫助我們在任何環境中徹底玩轉Java類路徑。