日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java是如何加载资源文件的?(源码解毒)

發(fā)布時(shí)間:2025/4/14 java 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java是如何加载资源文件的?(源码解毒) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? ? ? ? ?上文提到應(yīng)老板要求開(kāi)發(fā)一個(gè)測(cè)試工具能方便的加載存于文件中的測(cè)試參數(shù),當(dāng)時(shí)考慮既然是測(cè)試,把測(cè)試參數(shù)文件和測(cè)試類(lèi)放在一起豈不是很方便,但是老板說(shuō):我的需求是你把測(cè)試參數(shù)文件放到統(tǒng)一文件夾下比如resources目錄下,當(dāng)然你做的這個(gè)也可以保留。 好吧,既然老板都說(shuō)了,我就開(kāi)干唄,主要問(wèn)題是如何在CaseloaderSupplier中獲取resources文件夾的路徑,很多人的第一反應(yīng)是直接new File("/src/test/resources")不行嗎? 當(dāng)然是不行的,因?yàn)橥ㄟ^(guò)maven編譯以后會(huì)把資源文件拷貝到target目錄下邊,直接通過(guò)file是定位不到的。唯一的解決方案(我目前感覺(jué)是唯一的)是使用Java提供的抽象方法:resources。可以通過(guò)Class.getResources()或者ClassLoader.getResources()。 Class.getResource底層也是將加載任務(wù)委托給ClassLoader做的。 當(dāng)然,在做這個(gè)之前對(duì)resource不是太熟悉,只是偶爾會(huì)調(diào)用一下接口,所以感覺(jué)還是詳細(xì)了解一下resource心里有底一點(diǎn),google了很多文章,大概意思是getResource("")獲取的是當(dāng)前調(diào)用類(lèi)路徑,比如當(dāng)前類(lèi)路徑為file:/Users/caiyao/workservice/test-caseloader/target/classes/base/ (打包以后的),getResource("/")這個(gè)獲取的是classpath的根目錄,比如file:/Users/caiyao/workservice/test-caseloader/target/classes/(注意:沒(méi)有包名)。問(wèn)題是老板要求測(cè)試參數(shù)類(lèi)要在test包下的resources目錄里,這樣獲取的是main的包下面的,那么怎樣才能讓在測(cè)試的時(shí)候(執(zhí)行@Test方法)加載test包下的資源呢? 當(dāng)然在思考這個(gè)問(wèn)題之前,我還是在執(zhí)行@Test時(shí)試了一下,令人迷惑的是,在執(zhí)行@Test的時(shí)候拿到的已經(jīng)是test包下的資源了!!!file:/Users/caiyao/workservice/test-caseloader/target/test-classes/ 如何辦到的,詳細(xì)看下源碼:

首先看下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來(lái)執(zhí)行的。進(jìn)入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獲取資源和加載類(lèi)是同樣的道理,使用的是雙親委托機(jī)制,首先執(zhí)行父類(lèi)加載器的getResource方法,如果父類(lèi)加載器為空則執(zhí)行Bootstrap的類(lèi)加載器,如果父類(lèi)加載的getResource沒(méi)有獲取到值
再?gòu)淖约旱纳舷挛牟檎屹Y源,該方法真正執(zhí)行操作的是findResource(name)這個(gè)步驟,進(jìn)入java.lang.ClassLoader#findResource,ClassLoader的默認(rèn)實(shí)現(xiàn)直接返回null,應(yīng)該看ClassLoader的實(shí)現(xiàn)類(lèi)URLClassLoader
(從很少的源碼閱讀經(jīng)驗(yàn)中我發(fā)現(xiàn)通過(guò)IDE斷點(diǎn)調(diào)試能避免很多不必要的代碼閱讀,讓注意力更集中于關(guān)注的這條線),從調(diào)試的過(guò)程可以看出,最后的結(jié)果是由AppClassLoader返回,這點(diǎn)很重要,后面要用到: 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方法是一個(gè)native方法,無(wú)法通過(guò)IDE進(jìn)去調(diào)試,但是可以對(duì)ucp.findResource(name,true)打斷點(diǎn),通過(guò)調(diào)試可以看出正是該方法的執(zhí)行返回了
file:/Users/caiyao/workservice/test-caseloader/target/test-classes/,要注意一點(diǎn)這里調(diào)用的findResource是通過(guò)該類(lèi)的一個(gè)成員屬性u(píng)cp中進(jìn)去的,ucp持有該方法的一些上下文,斷點(diǎn)進(jìn)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是通過(guò)this.getNextLoader(var4, var5))返回的Loader里得到的,而this正是上面URLClassLoader的成員屬性u(píng)cp,回到URLClassLoader類(lèi)中尋找ucp的初始化代碼,可以看到一個(gè)單參數(shù)的構(gòu)造方法: 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是重什么地方傳遞過(guò)來(lái)的,我以前大致了解過(guò)Java的類(lèi)加載過(guò)程,知道類(lèi)加載器是由AppClassLoader / ExtClassLoader / BootstrapClassLoader這樣的一個(gè)層級(jí)結(jié)構(gòu)組成,它們由Launcher初始化,從上面
已經(jīng)得知最后的資源路徑是由AppClassLoader得到,所以可以猜測(cè)AppClassLoader是URLClassLoader的子類(lèi),可能在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);
}
});
}
證實(shí)了猜想,AppClassLoader從java.class.path變量中獲取資源路徑,而java.class.path這個(gè)變量由在什么地方設(shè)置的呢? 肯定在執(zhí)行java命令時(shí)設(shè)置的參數(shù),可以 ps aux |grep Test1(Test1是我測(cè)試類(lèi)的名字)查看到
當(dāng)前執(zhí)行的測(cè)試進(jìn)程的信息,果然在執(zhí)行Java命令時(shí)把如下兩個(gè)路徑都加到了classpath中:

/Users/caiyao/workservice/test-caseloader/target/test-classes
/Users/caiyao/workservice/test-caseloader/target/classes

而且test-classes在前面,從sun.misc.URLClassPath#findResource中的代碼可以看出,在遍歷classpath的時(shí)候,一旦發(fā)現(xiàn)了一個(gè)存在就不會(huì)再往后遍歷,所以在執(zhí)行test方法的時(shí)候只會(huì)拿到test目錄,至于為什么在執(zhí)行Test的時(shí)候自動(dòng)把test的資源路徑加到了classpath里,這個(gè)我沒(méi)有再深入研究,猜測(cè)應(yīng)該是maven做的操作,因?yàn)閠arget這個(gè)目錄就是maven的規(guī)定。

終于找到原因,可以安心的寫(xiě)代碼了~~~

轉(zhuǎn)載于:https://www.cnblogs.com/caiyao/p/9306719.html

總結(jié)

以上是生活随笔為你收集整理的Java是如何加载资源文件的?(源码解毒)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。