和Java類路徑(classpath)打交道的過程中,開發者偶然會碰到麻煩。這是因為,類裝載器實際裝入的是哪一個類有時並不顯而易見,當應用程序的classpath包含大量的類和目錄時,情況尤其嚴重。本文將提供一個工具,它能夠顯示出被裝入類文件的絕對路徑名。
一、Classpath基礎
Java虛擬機(JVM)借助類裝載器裝入應用程序使用的類,具體裝入哪些類根據當時的需要決定。CLASSPATH環境變量告訴類裝載器到哪裡去尋找第三方提供的類和用戶定義的類。另外,你也可以使用JVM命令行參數-classpath分別為應用程序指定類路徑,在-classpath中指定的類路徑覆蓋CLASSPATH環境變量中指定的值。
類路徑中的內容可以是:文件的目錄(包含不在包裡面的類),包的根目錄(包含已打包的類),包含類的檔案文件(比如。zip文件或者。jar文件)。在Unix家族的系統上,類路徑的各個項目由冒號分隔,在MS Windows系統上,它們由分號分隔。
類裝載器以委托層次的形式組織,每一個類裝載器有一個父類裝載器。當一個類裝載器被要求裝載某個類時,它在嘗試自己尋找類之前會把請求先委托給它的父類裝載器。系統類裝載器,即由安裝在系統上的JDK或JRE提供的默認類裝載器,通過CLASSPATH環境變量或者-classpath這個JVM命令行參數裝入第三方提供的類或者用戶定義的類。系統類裝載器委托擴展類裝載器裝入使用Java Extension機制的類。擴展類裝載器委托自舉類裝載器(bootstrap class loader)裝入核心JDK類。
你可以自己開發非凡的類裝載器,定制JVM如何動態地裝入類。例如,大多數Servlet引擎使用定制的類裝載器,動態地裝入那些在classpath指定的目錄內發生變化的類。
必須非凡注重的是(也是令人吃驚的是),類裝載器裝入類的次序就是類在classpath中出現的次序。類裝載器從classpath的第一項開始,依次檢查每一個設定的目錄和壓縮文件,嘗試找出待裝入的類文件。當類裝載器第一次找到具有指定名字的類時,它就把該類裝入,classpath中所有余下的項目都被忽略。
看起來很簡單,對吧?
二、可能出現的問題
不管他們是否願意承認,初學者和富有經驗的Java開發者都一樣,他們都曾經在某些時候(通常是在那些最糟糕的情形下)被冗長、復雜的classpath欺騙。應用程序所依靠的第三方類和用戶定義類的數量逐漸增長,classpath也逐漸成了一個堆積所有可能的目錄和檔案文件名的地方。此時,類裝載器首先裝載的究竟是哪一個類也就不再顯而易見。假如classpath中包含重復的類入口,這個問題尤其突出。前面已經提到,類裝載器總是裝載第一個它在classpath中找到的具有合適名字的類,從實際效果看,它“隱藏”了其他具有合適名字但在classpath中優先級較低的類。
假如不小心,你很輕易掉進這個classpath的陷阱。當你結束了一天漫長的工作,最後為了讓應用程序使用最好、最新的類,你把一個目錄加入到了classpath,但與此同時,你卻忘記了:在classpath的另一個具有更高優先級的目錄下,存放著該類的另一個版本!
三、一個簡單的classpath工具
優先級問題是扁平路徑聲明方法與生俱來固有的問題,但它不是只有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中哪一個類先被裝載,你必須深入到類裝載器的思考方法。事實上,具體實現的時候並沒有聽起來這麼復雜——你只需直接詢問類裝載器就可以了!