難經2:URL.getFile(),是你讓老虎落入陷阱?

[問題]

 由於開發需要,編寫了一個簡單的單元測試框架,在基類中從類路徑加載資源,並執行初始化動作(類似Spring測試基類),利用ClassLoader,加載資源自是駕輕就熟。我自己通過繼承這個測試基類,編寫了不少測試類,感覺還算方便,也沒有出過什麼問題。不過,近日其他同事Z在用這個測試基類時,卻經常出現加載不到資源的問題,很奇怪。

[探幽]

既然是加載不到資源,還是第一反應,肯定是資源路徑有問題,可是我的環境下並沒有報這個錯啊,看看Z同事的測試類,要加載的資源文件明明就在那,類路徑指定也沒有問題啊;掃了一眼,看見資源文件的命名是中文的,不過我並沒有在意,因爲我用中文測過,沒有問題。

 

不是中文問題,那可能是其他什麼原因呢?苦思不得其解,回到自己的機器上,調試了一下程序,本機運行依然沒有問題,重新審查加載資源的方法實現,用的ClassLoader定位取得URL,再用URL.getFile()取得實際路徑,最後new File()讀取文件,沒什麼問題啊?

 

要真有問題,難道是URL.getFile()不正確?想到這一點,再想想Z君的開發環境,他是在Java5下開發,而我還是堅守者1.4.2陣地,難道?難道Java5下會有問題?我飛快的抓起鼠標,把編譯環境改爲Tiger,運行,出錯!Java5下確實找不到資源!斷點調試發現,在Java5下,URL.getFile()返回的資源位置是未解碼的,空格和中文成了URL轉碼格式。URL.getFile(),真的是你讓Tiger老虎落入陷阱麼?

 

爲了確認問題,我單獨建了一個測試項目,寫了一個測試類,分別在Java1.4.2、Java5、Java6下運行, 結果確實讓我驚訝:

public class URLTest {
	public static void main(String[] args) {
		
		URL url1 = URLTest.class.getResource("te st.txt");
		URL url2 = URLTest.class.getResource("中文.txt");
		
		System.out.println("te st.txt => " + url1.getFile() + ", exist => " + new File(url1.getFile()).exists());
		System.out.println("中文.txt => " + url2.getFile() + ", exist => " + new File(url2.getFile()).exists());
	} 
}

其中“te st.txt”和"中文.txt"是和測試類放在一起的測試文件。

 

 Java1.4.2下輸出:

te st.txt => /E:/CODE/Test/bin/url/te st.txt, exist => true
中文.txt => /E:/CODE/Test/bin/url/中文.txt, exist => true

 

Java5和Java6均輸出:

te st.txt => /E:/CODE/Test/bin/url/te%20st.txt, exist => false
中文.txt => /E:/CODE/Test/bin/url/%e4%b8%ad%e6%96%87.txt, exist => false

 

 

看來,原來URL.getFile()是在1.4.2下能自動解碼的,到了老虎和野馬那裏卻不靈了,從而導致找不到資源文件。找到了問題,我長舒了一口氣。

 

既然問題已經找到,答案呼之欲出,不過,爲什麼Java5以後要修改URL的行爲,不再自動解碼呢?我還是有些疑惑。

 

接下來,我一不做二不休,不就是類路徑麼,我再改,把編譯路徑從“bin”改成“bin all”,中間加空格,再次運行。。。。。。

 

Java1.4.2下輸出:

te st.txt => /E:/CODE/Test/bin%20all/url/te st.txt, exist => false
中文.txt => /E:/CODE/Test/bin%20all/url/中文.txt, exist => false

Java5和Java6均輸出:

te st.txt => /E:/CODE/Test/bin%20all/url/te%20st.txt, exist => false
中文.txt => /E:/CODE/Test/bin%20all/url/%e4%b8%ad%e6%96%87.txt, exist => false

 

哈哈,Java1.4.2的URL終於露餡了,原來它的解碼是不完全的!原來URL.getFile()本身就是陷阱啊!即使是在1.4.2下,它也只是給我們一個迷惑的假相。

 

回頭想想,爲什麼Sun要冒着版本不兼容的危險,也要修改URL的行爲呢?這或許就是其深層次的原因。

 

[解難]

搞清楚了問題的癥結,按方抓藥即可。URLDecoder正是用來幹這個活的,同時祭出Google,看看同志們是不是早就搞定這個問題了,放眼望去,URL.getFile()搜出來的結果還真不少啊,連帶上URL.getPath()也受到牽連,而且URL.getFile()和URL.getPath()得到的路徑還可能不一致!又是一個陷阱啊。

 

終解,用URLDecoder:

System.out.println("te st.txt =decode=> " + URLDecoder.decode(url1.getFile(), "UTF-8"));
System.out.println("中文.txt =decode=> " + URLDecoder.decode(url2.getFile(), "UTF-8"));

 

另外,URI也是可以自動解碼的:

System.out.println("te st.txt =uri=> " + new URI(url1.getFile()).getPath());
System.out.println("te st.txt =uri=> " + new URI(url2.getFile()).getPath());

 

在Java5和Java6下運行,二者皆輸出正常:

te st.txt => /E:/CODE/Test/bin%20all/url/te%20st.txt, exist => false
中文.txt => /E:/CODE/Test/bin%20all/url/%e4%b8%ad%e6%96%87.txt, exist => false
te st.txt =decode=> /E:/CODE/Test/bin all/url/te st.txt
中文.txt =decode=> /E:/CODE/Test/bin all/url/中文.txt
te st.txt =uri=> /E:/CODE/Test/bin all/url/te st.txt
te st.txt =uri=> /E:/CODE/Test/bin all/url/中文.txt

 

不過,1.4.2下,URI又可能是一個陷阱:

te st.txt => /E:/CODE/Test/bin/url/te st.txt, exist => true
中文.txt => /E:/CODE/Test/bin/url/中文.txt, exist => true
te st.txt =decode=> /E:/CODE/Test/bin/url/te st.txt
中文.txt =decode=> /E:/CODE/Test/bin/url/中文.txt
java.net.URISyntaxException: Illegal character in path at index 24: /E:/CODE/Test/bin/url/te st.txt
	at java.net.URI$Parser.fail(URI.java:2752)
	at java.net.URI$Parser.checkChars(URI.java:2925)
	at java.net.URI$Parser.parseHierarchical(URI.java:3009)
	at java.net.URI$Parser.parse(URI.java:2967)
	at java.net.URI.<init>(URI.java:574)
	at url.URLTest.main(URLTest.java:24)
Exception in thread "main" 

URI對於不完整解碼的路徑,會拋語法異常。 

 

看來,還是Decoder.decode()是最終正解啊,用URI的同學要小心了,請保證你的應用跑在Java5上。

另,看到JavaEye裏有兄弟自己替換"%20"爲空格,來解決臨時這個問題,這樣很不好吧,有中文怎麼辦?

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章