Java是如何加载资源文件的?(源码解毒)
? ? ? ? ?上文提到應老板要求開發一個測試工具能方便的加載存于文件中的測試參數,當時考慮既然是測試,把測試參數文件和測試類放在一起豈不是很方便,但是老板說:我的需求是你把測試參數文件放到統一文件夾下比如resources目錄下,當然你做的這個也可以保留。 好吧,既然老板都說了,我就開干唄,主要問題是如何在CaseloaderSupplier中獲取resources文件夾的路徑,很多人的第一反應是直接new File("/src/test/resources")不行嗎? 當然是不行的,因為通過maven編譯以后會把資源文件拷貝到target目錄下邊,直接通過file是定位不到的。唯一的解決方案(我目前感覺是唯一的)是使用Java提供的抽象方法:resources。可以通過Class.getResources()或者ClassLoader.getResources()。 Class.getResource底層也是將加載任務委托給ClassLoader做的。 當然,在做這個之前對resource不是太熟悉,只是偶爾會調用一下接口,所以感覺還是詳細了解一下resource心里有底一點,google了很多文章,大概意思是getResource("")獲取的是當前調用類路徑,比如當前類路徑為file:/Users/caiyao/workservice/test-caseloader/target/classes/base/ (打包以后的),getResource("/")這個獲取的是classpath的根目錄,比如file:/Users/caiyao/workservice/test-caseloader/target/classes/(注意:沒有包名)。問題是老板要求測試參數類要在test包下的resources目錄里,這樣獲取的是main的包下面的,那么怎樣才能讓在測試的時候(執行@Test方法)加載test包下的資源呢? 當然在思考這個問題之前,我還是在執行@Test時試了一下,令人迷惑的是,在執行@Test的時候拿到的已經是test包下的資源了!!!file:/Users/caiyao/workservice/test-caseloader/target/test-classes/ 如何辦到的,詳細看下源碼:
首先看下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);
}
c1.getResource(name)這句可以看出Class是把獲取資源委托給ClassLoader來執行的。進入ClassLoader的getResoure方法(java.lang.ClassLoader#getResource): public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
從這里可以看出Java獲取資源和加載類是同樣的道理,使用的是雙親委托機制,首先執行父類加載器的getResource方法,如果父類加載器為空則執行Bootstrap的類加載器,如果父類加載的getResource沒有獲取到值
再從自己的上下文查找資源,該方法真正執行操作的是findResource(name)這個步驟,進入java.lang.ClassLoader#findResource,ClassLoader的默認實現直接返回null,應該看ClassLoader的實現類URLClassLoader
(從很少的源碼閱讀經驗中我發現通過IDE斷點調試能避免很多不必要的代碼閱讀,讓注意力更集中于關注的這條線),從調試的過程可以看出,最后的結果是由AppClassLoader返回,這點很重要,后面要用到: public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
} AccessController.doPrivileged方法是一個native方法,無法通過IDE進去調試,但是可以對ucp.findResource(name,true)打斷點,通過調試可以看出正是該方法的執行返回了
file:/Users/caiyao/workservice/test-caseloader/target/test-classes/,要注意一點這里調用的findResource是通過該類的一個成員屬性ucp中進去的,ucp持有該方法的一些上下文,斷點進sun.misc.URLClassPath#findResource方法: public URL findResource(String var1, boolean var2) {
int[] var4 = this.getLookupCache(var1);
URLClassPath.Loader var3;
for(int var5 = 0; (var3 = this.getNextLoader(var4, var5)) != null; ++var5) {
URL var6 = var3.findResource(var1, var2);
if (var6 != null) {
return var6;
}
}
return null;
}
從該方法可以看出URL是通過this.getNextLoader(var4, var5))返回的Loader里得到的,而this正是上面URLClassLoader的成員屬性ucp,回到URLClassLoader類中尋找ucp的初始化代碼,可以看到一個單參數的構造方法: public URLClassLoader(URL[] urls) {
super();
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}然后需要找到urls是重什么地方傳遞過來的,我以前大致了解過Java的類加載過程,知道類加載器是由AppClassLoader / ExtClassLoader / BootstrapClassLoader這樣的一個層級結構組成,它們由Launcher初始化,從上面
已經得知最后的資源路徑是由AppClassLoader得到,所以可以猜測AppClassLoader是URLClassLoader的子類,可能在Launcher中初始化,從Launcher中可以找到如下代碼: static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
證實了猜想,AppClassLoader從java.class.path變量中獲取資源路徑,而java.class.path這個變量由在什么地方設置的呢? 肯定在執行java命令時設置的參數,可以 ps aux |grep Test1(Test1是我測試類的名字)查看到
當前執行的測試進程的信息,果然在執行Java命令時把如下兩個路徑都加到了classpath中:
/Users/caiyao/workservice/test-caseloader/target/test-classes
/Users/caiyao/workservice/test-caseloader/target/classes
而且test-classes在前面,從sun.misc.URLClassPath#findResource中的代碼可以看出,在遍歷classpath的時候,一旦發現了一個存在就不會再往后遍歷,所以在執行test方法的時候只會拿到test目錄,至于為什么在執行Test的時候自動把test的資源路徑加到了classpath里,這個我沒有再深入研究,猜測應該是maven做的操作,因為target這個目錄就是maven的規定。
終于找到原因,可以安心的寫代碼了~~~
轉載于:https://www.cnblogs.com/caiyao/p/9306719.html
總結
以上是生活随笔為你收集整理的Java是如何加载资源文件的?(源码解毒)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: js课程 5-14 js如何实现控制动画
- 下一篇: Confluence 6 使用 WebD