Java Gradle專案中的資源正確獲取
阿新 • • 發佈:2018-12-05
引言
一個Java Gradle專案會涉及到資源的訪問. 一般情況下會將當前專案所需的資原始檔全部放置於resources資料夾下, 無論是main檔案下的source code 還是test資料夾下的test code. 都或多或少的涉及到獲取resources資料夾下的資源. 本文主要目的就是詳細的總結一下如何獲取resources資料夾下的資源.
兩個getResource方法
來看一個簡單的Java Gradle專案(稱呼其為simpleresource)的專案結構
首先這個project執行build之後會在根目錄下建立一個out目錄, 這個目錄存放所有的編譯結果(class檔案以及資原始檔). 如上圖所示production資料夾對應的是source code而test資料夾對應的是test code.
所有的資源都會儲存在resources資料夾內部. 當程式執行時獲取的資源就是這個resources資料夾下的資源.
我們使用最多的獲取資源的方法有兩個
Class.getResource
和
ClassLoader.getResource
但是這兩個方法傳遞引數與結果不同, 下面詳細分析一下這兩個方法引數以及返回值.
先看
ClassLoader
getResource
方法. 只需要獲取類載入器物件即可(獲取方式不再贅述). 先看這個方法的API文件相關的描述:
Finds the resource with the given name. A resource is some data (images, audio, text, etc) that can be accessed by class code in a way that is independent of the location of the code.
The name of a resource is a '/'-separated path name that identifies the resource.
This method will first search the parent class loader for the resource; if the parent is null the path of the class loader built-in to the virtual machine is searched. That failing, this method will invoke findResource(String) to find the resource.
從這個描述中可以得知提供資源的路徑(我理解的是相對路徑), 正常情況下該方法會返回資源完整的URL. 傳遞的引數有一個重要的注意事項, 就是傳遞的引數不能夠以/
開始, 這也是我為什麼稱呼這個引數為資源的相路徑. 舉個例子
URL test = this.getClass().getClassLoader().getResource("/");
執行上述程式碼返回的結果是:
參考simpleresource的專案結構, 正確獲取
com.mainres
下的檔案的正確做法是:
String name = "com/mainres/testmain.txt";
URL test = this.getClass().getClassLoader().getResource(name);
結果為:
如果在表示資源路徑的字串中加上
/
那麼獲取到的URL依然為null
String name = "/com/mainres/testmain.txt";
URL test = this.getClass().getClassLoader().getResource(name);
宗上所述, 使用類載入器獲取資源的方式傳遞的引數為資源相對路徑(相對於resources資料夾的路徑), 既然是相對路徑自然引數 不能夠以
/
開始.
有一個問題需要注意, 當傳遞引數為空字串的時候, 得到路徑其實是classes資料夾的絕對路徑, 但當傳遞一個正確的資源路徑相對字串時, 得到路徑卻是resources資料夾下的資源路徑.
String name = "";
URL test = this.getClass().getClassLoader().getResource(name);
我的理解是本質上是通過此方法獲取的其實類載入器載入的class位元組碼目錄, 所以使用空字串會看到實際輸出的是classes資料夾絕對路徑, 當傳遞正確的資源路徑的時候, 程式碼層面做轉換, 轉而獲取與classes資料夾同級的resources資料夾下的資源.
再看
Class
中的
getResurce
方法
由於這個方法傳遞引數是否是以
/
開頭會產生不同的結果, 且使用這個方法也比較容易和
ClassLoader
中的
getResource
方法搞混淆, 所以本文多次強調傳遞的引數是否以
/
開始.
首先看傳遞引數為
""
和
/
的兩種情況得到的結果:
使用空字串:
String name = "";
URL test = this.getClass().getResource(name);
執行結果:
使用
/
String name = "/";
URL test = this.getClass().getResource(name);
執行結果為:
最大的區別是使用空字串
""
獲取的路徑是相對於包的目錄, 而使用
/
獲取的路徑是類載入器載入class檔案的目錄, 這個和
ClassLoader
的
getResource
方法傳遞
""
字串的結果是一樣的. 所以如果要正確的獲取到資原始檔, 那麼使用
Class
的
getResource
方法如下:
String name = "/com/mainres/testmain.txt";
URL test = this.getClass().getResource(name);
執行結果:
所以綜上所述, 一個簡單的防止兩個方法傳遞引數搞混淆的記憶方式就是使用
Class
的
getResource
方法需要加
/
而使用
ClassLoader
的
getResource
方法不要加
/
.
其實參考
Class
類中的
getResource
方法:
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
中的getResource
方法. 其中resolveName
這個方法對傳遞的引數做了轉換.
private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}
當傳遞的引數帶有/
時候, resolveName
會將/
去除後的字串返回, 最後呼叫ClassLoader
中的 getResource
方法.
小結
本文對比了一下Class
和 ClassLoader
中的getResource
方法的差異,如果單純從資源的獲取角度來看最終呼叫的都是ClassLoader
中的getResource
方法. 簡單記憶即是使用Class
的getResource
方法資源路徑需要加/
而使用ClassLoader
中的getResource
方法則不需要加/
.