JAVA中路徑問題總結Class.getResourceAsStream() & ClassLoader的getR...
Class.getResourceAsStream() & ClassLoader的getResourceAsStream()
注意兩點:
1,用Class.getResourceAsStream() 時,路徑應該是以"/"開頭的,如:
mypackage.Hello.class.getResourceAsStream("/config/config.ini");
2,如果直接用ClassLoader的getResourceAsStream() 不用以"/"開頭.如,
mypackage.Hello.class.getResourceAsStream("config/config.ini");
3注意檔案放置的位置,應該放在classpath下.如:工程檔案中的classes下.
大家自己試試.
"ClassLoader提供了兩個方法用於從裝載的類路徑中取得資源:
public URL getResource(String name);
public InputStream getResourceAsStream(String name);
這裡name是資源的類路徑,它是相對與“/”根路徑下的位置。getResource得到的是一個URL物件來定位資源,而getResourceAsStream
取得該資源輸入流的引用保證程式可以從正確的位置抽取資料。
然而,程式中呼叫的通常並不是ClassLoader的這兩個方法,而是Class的getResource和getResourceAsStream方法,因為Class物件可
以從你的類得到(如YourClass.class或YourClass.getClass()),而ClassLoader則需要再呼叫一次YourClass.getClassLoader()方法,
但根據JDK文件的說法,Class物件的這兩個方法其實是“委託”(delegate)給裝載它的ClassLoader來做的,所以只需要使用Class物件
的這兩個方法就可以了。
舉例說明:
mypackage.Hello.class.getResourceAsStream("/config/config.ini");
從classpath下的config相對路徑中讀取config.ini"
這兩個方法還是略有區別的, 以前一直不加以區分,直到今天發現要寫這樣的程式碼的時候執行
錯誤, 才把這個問題澄清了一下。
基本上,兩個都可以用於從 classpath 裡面進行資源讀取, classpath包含classpath中的路徑
和classpath中的jar。
兩個方法的區別是資源的定義不同, 一個主要用於相對與一個object取資源,而另一個用於取相對於classpath的
資源,用的是絕對路徑。
在使用Class.getResourceAsStream 時, 資源路徑有兩種方式, 一種以 / 開頭,則這樣的路徑是指定絕對
路徑, 如果不以 / 開頭, 則路徑是相對與這個class所在的包的。
在使用ClassLoader.getResourceAsStream時, 路徑直接使用相對於classpath的絕對路徑。
舉例,下面的三個語句,實際結果是一樣的:
com.explorers.Test.class.getResourceAsStream("abc.jpg")
= com.explorers.Test.class.getResourceAsStream("/com/explorers/abc.jpg")
= ClassLoader.getResourceAsStream("com/explorers/abc.jpg")
主題:資源獲取:
在開發java程式的過程中,我們經常要做的一件事就是獲取資源。那麼什麼是資源呢?說白了,在計算機裡那就是一堆資料。只是這堆資料對我們的java程式有多種表現形式,一般來說有File,URL,InputStream等等。而單就檔案這一項就有很多種:配置檔案,java類檔案,jps檔案,圖片、css、js檔案等等。面對這林林總總的資源,我們在設計一個讀取資源的介面時,就需要針對不同形式的資源提供方法,這樣就導致我們的介面還是與實際的資源形式繫結在一起,未能完全的抽象。另外,在java程式中資源的存放位置也是各異的。有的存放在classpath中,有的存放在檔案系統中,有的存放在web應用中。而對於不同位置的資源,java程式獲取這些資源的方法各有不同。
A、獲取classpath中的資源:
Java程式碼
URL url = this.getClass().getResource("resource_name");
URL url = this.getClass().getClassLoader().getResource("resource_name");
URL url = Thread.currentThread().getContextClassLoader().getResource("resource_name");
URL url = this.getClass().getResource("resource_name");
URL url = this.getClass().getClassLoader().getResource("resource_name");
URL url = Thread.currentThread().getContextClassLoader().getResource("resource_name");
那麼在jdk中為什麼又提供了三種方式來獲取classpath下的資源呢?這其中是有些來頭的。
第一行程式碼中是利用Class類的例項來獲取,第二行程式碼是使用載入當前類的classloader來獲取。看下jdk中的原始碼會發現class類的例項最後還是委託載入他的classloader來獲取資源的。
Java程式碼
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
從上面的程式碼中可以看出,對於資源的載入並沒有像類載入所採用的雙親委託機制。而是當前類的classloader不為null的情況下先從當前類的classloader中載入資源。而只有當前類的classloader為null的時候才從system classloader中去載入資源。這樣可以方便我們自定義配置類覆蓋一些預設配置。當然,j2se應用中如果沒有特別定製classloader時,我們自己寫的類都是被system classloader載入的。到底利用class去獲取資源和利用classloader去獲取資源有什麼區別呢?區別就在
resolveName(name)這個方法中。兩種方式對於資源名稱的表示方式不同。下面是一個簡單的包結構,/表示類路徑的根
/
|-com.cn.test
|-Test.class
|-test2.txt
|-test1.txt
Java程式碼
// 獲取與當前類在同一個包下的資源
URL url1 = this.getClass().getResource("test2.txt");
// 獲取com.cn.test包下的資源,需加/
URL url2 = this.getClass().getResource("/com/cn/test/test2.txt");
// 獲取類路徑根下的資源
URL url3 = this.getClass().getClassLoader().getResource("test1.txt");
// 獲取包com.cn.test包下的資源
URL url4 = this.getClass().getResource("com/cn/test/test2.txt");
// 獲取與當前類在同一個包下的資源
URL url1 = this.getClass().getResource("test2.txt");
// 獲取com.cn.test包下的資源,需加/
URL url2 = this.getClass().getResource("/com/cn/test/test2.txt");
// 獲取類路徑根下的資源
URL url3 = this.getClass().getClassLoader().getResource("test1.txt");
// 獲取包com.cn.test包下的資源
URL url4 = this.getClass().getResource("com/cn/test/test2.txt");
而第三利用當前執行緒的contextClassLoader來獲取資源的解釋可以參考我的另一篇
B、獲取檔案系統中的資源
Java程式碼
// 1、獲得File物件
File file = new File("test.txt");
// 2、獲得File物件的位元組流
InputStream in = new FileInputStream(file);
// 1、獲得File物件
File file = new File("test.txt");
// 2、獲得File物件的位元組流
InputStream in = new FileInputStream(file);
值得注意的是在File的建構函式File(String name) 中的name引數可以是相對路徑和絕對路徑。相對路徑是相對於System.getProperties("user.dir")的。
C、獲取web應用中的資源
Java程式碼
servletContext.getResourceAsStream(resource_name);
servletContext.getResourceAsStream(resource_name);
resource_names為相對於webroot的路徑表示。例如獲取web.xml,resource_name表示為"/WEB-INF/web.xml"
面對上面介紹的各種資源表現形式和存放位置,難道java中就沒有提供一個統一處理方式嗎?有,java.net.URL。
從名稱上來看 URL(Uniform Resource Locator) 統一資源定位器。看起來很好很強大。但很多時候使用它並不能定位到我們需要的資源。
首先,它jdk中體統的URL能訪問的協議非常有限(當然可以進行擴充套件,不過很麻煩);常用的有http,file,ftp等等。並沒有提供對classpath和servletContext中的資源的獲取方法。
另外,它沒有提供判斷資源是否存在的方法。每次只有等我們真正去獲取資源的時候丟擲異常才能知道資源無法獲取。
其次,URL這個類的職責未劃分清楚,既用來表示資源有用來獲取其資源。
===============================================
◆ 一般情況下,我們都使用相對路徑來獲取資源,這樣的靈活性比較大.
比如當前類為com/bbebfe/Test.class
而影象資源比如sample.gif應該放置在com/bbebfe/sample.gif
而如果這些影象資源放置在icons目錄下,則應該是com/bbebfe/icons/sample.gif
通過當前類檔案的路徑獲取資源主要有如下幾種方式:
· 假設當前類為com.bbebfe.Test
· 包所在的資料夾為bin
String imageName = "icons/sample.gif"
1, 通過Class.getResource()定位類路徑下的資源(bin/com/bbebfe/icons/sample.gif)
Class clazz = this.getClass();
URL url = clazz.getResource(imageName);
2, 通過ClassLoader.getResource()定位包的根目錄下的資源(bin/icons/sample.gif)
Class clazz = this.getClass();
URLClassLoader loader = (URLClassLoader)clazz.getClassLoader();
URL url = loader.getResource(imageName);
3, 通過ClassLoader.findResource()提供自己定製的方式定位資源
URL url = loader.findResource(imageName);
◆ 那麼這三種方法有那些區別, 我們應該在何時使用哪種方法呢?
· Class.getResource() 方法
該方法實際通過該Class的Class Loader的getResource()方法來獲得資源, 在呼叫ClassLoader的getResource()方法之前, Class.getResource()方法會對資源名稱做一定的處理,構建一個該資源的絕對名稱(absolute name, 大意是:
+ 如果資源名稱以'/'('/u002f') 開始, 則資源的絕對名稱是'/'以後的部分.
如果imageName是"/icons/sample.gif", 則在這裡會變成"icons/sample.gif"
+ 否則對於其他情況, 絕對名稱將是如下形式(給資源名稱的前面加上modified_package_name/):
modified_package_name/resource_name (修正的包名稱/資源名稱)
其中修正的包名稱含義是將當前物件所在的包名稱中的'.'('/u002e')替換為'/'
如果ClassLoader.getResource()方法返回一個值為null的URL, 則Class.getResource()方法最終會將資源請求交給ClassLoader.getSystemResource(java.lang.String).
· ClassLoader.getResource() 方法
該對資源進行查詢, 資源的名稱是以'/'分隔的路徑, 這個方法首先查詢自己的父親ClassLoader, 由自己的父ClassLoader來查詢資源(實際上, 如果父親的父親不是空, 則父親仍會向上提交查詢請求). 如果自己的父ClassLoader是null, 則查詢Java虛擬機器中內建的class loader, 並將資源請求提交給它們, 如果這些操作都失敗了, 則ClassLoader會呼叫自己的findResource()方法來查詢資源.
· ClassLoader.findResource() 方法
該方法在內部查詢指定的資源, 如果你實現了自己的Class Loader,則應該過載這個方法以自己特定的方式來查詢類檔案和資源.
◆ 通過以上的總結, 我們可以看到三點.
1, 無論是getResource(), 還是findResource(), 這些方法都只是資源的定位方法, 最終都只是返回一個URL, 只是對資源的定位而已, 我們隨後應通過自己的方法來讀取這些資源. 而在Class和ClassLoader中還定義的有getResourceAsStream方法, 該方法是getResource的增強版, 這裡就不介紹了.
2, 如果需要以類為相對路徑查詢資源, 則應該呼叫Class.getResource()方法, 不要直接呼叫ClassLoader.getResource()方法. 另外, 除非是你自己定義了ClassLoader並重載了findResource方法,否則也不要直接呼叫ClassLoader.findResource方法, 因為在Class.getResource()方法中會對資源名稱作一定的處理, 這在上面介紹了, 下面舉個例項:
假設我的當前類在Eclipse工程Database下, 類所在的包是com.bbebfe.test, 而icons目錄放在bin/com/bbebfe/test/目錄下, 我需要得到icons/sample.gif檔案的URL, 則呼叫this.getClass().getResource()得到的URL是:
file:/E:/MyLife/MyProjects/Eclipse3.2/Database/bin/com/bbebfe/test/icons/disremove.gif
3, 有時候我們希望某個jar庫的影象資源在同一個icons下統一管理, 而不是為每個包下面的Class建一個icons, 也就是說需要以庫為相對路徑來查詢資源, 此時則應該呼叫ClassLoader.getResource()方法, 舉個例子:
· 某個工程有如下的包結構:
com.bbebfe.ui
com.bbebfe.test
com.bbebfe.database
· 如果以類為相對路徑, 則在每個包下都必須建立一個icons目錄, 並放置相應的資原始檔. 如下:
com.bbebfe.ui/icons/...
com.bbebfe.test/icons/...
com.bbebfe.database/icons/...
· 而我們可能希望在根目錄下放置一個icons目錄, 把所有資源放置在這裡管理, 這樣還可以防止資源的重複. 就是如下形式
com.bbebfe.ui
com.bbebfe.test
com.bbebfe.database
icons/sample.gif ...
則此時我們應該呼叫ClassLoader.getResource方法, 由於它沒有對資源名稱作處理, 也就是說沒有將修正的包名新增到資源名稱前, 所以它會在類所在的包的根下去查詢資源.(執行java程式的語法是java com.bbebfe.ui.Test, 所以根目錄是com目錄的上級目錄).
◆ 最後, 在Java中對資源進行定位的方法有很多種, 在Eclipse原始碼中還有如下一段定位檔案資源的程式碼, 還沒有時間研究, 以後再談:
ProtectionDomain domain = Main.class.getProtectionDomain();
CodeSource source = null;
URL result = null;
if (domain != null)
source = domain.getCodeSource();//獲得code source
if (source != null)
result = source.getLocation();//獲得URL
String path = decode(result.getFile());//
// normalize to not have leading / so we can check the form
File file = new File(path);
path = file.toString().replace('//', '/');
// create a file URL (via File) to normalize the form (e.g., put
// the leading / on if necessary)
path = new File(path).toURL().getFile();
來自CSDN網站lgq_0714部落格