解決使用this.getClass().getResource()獲取文件時(shí)遇到的坑
使用this.getClass().getResource()獲取文件時(shí)遇到的坑
最近在工作中遇到需要讀取配置文件,然后第一想法就是將文件放到項(xiàng)目的resources
目錄下,
然后使用:
String fileName = "config/zh.md" String path = this.getClass().getResource("/").getPath() + fileName; System.out.println(path);// D:/example/exam01/target/classes/config/zh.md
在IDE工具中開發(fā)及Debug時(shí)一切都正常,但是打成Jar包發(fā)布到線上時(shí)就會(huì)出現(xiàn)java.io.FileNotFoundException
java.io.FileNotFoundException: file:/usr/local/exam01-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/config/zh.md (No such file or directory)
錯(cuò)誤信息也已經(jīng)很明顯了,就是因?yàn)槲募淮嬖?,但是在IDE中是可以正常運(yùn)行了,那為什么打成jar包放到服務(wù)器中就不行了呢?
仔細(xì)檢查報(bào)錯(cuò)路徑發(fā)現(xiàn)在磁盤確實(shí)不存在這樣一條路徑,因?yàn)槁窂綇?.../exam01-1.0-SNAPSHOT.jar/...
開始,后面的文件路徑都是打到Jar包中的,磁盤沒有后面 .../BOOT-INF/classes!/config/zh.md
這樣的目錄;
在Jar包中的文件在磁盤是沒有實(shí)際路徑的,所以這時(shí)候通過 this.getClass()..getResource()
無法獲取文件。
解決方式一
直接將需要的文件上傳到服務(wù)器指定的文件夾下,如果把文件路徑寫死,就太low了,也不符合編碼規(guī)范。
而且存在各種隱患例如:不同的環(huán)境發(fā)布到不同的服務(wù)器上,開發(fā)一個(gè)服務(wù)器,測試一個(gè)服務(wù)器,生產(chǎn)一個(gè)服務(wù)器,每個(gè)服務(wù)器中都要上傳一份;如果誤刪或者遷移項(xiàng)目忘記遷移這個(gè)文件就麻煩了;
解決方式二
可以通過 this.getClass()..getResourceAsStream("/config/zh.md")
能夠正常獲取到文件流。
xxx.class.getResource("") 和 xxx.class.getClassLoader().getResource("")
上面問題已經(jīng)解決了,我們看下xxx.class.getResource("") 和 **xxx.class.getClassLoader().getResource("")**的區(qū)別;
1.其實(shí)
class.getResource("/") == class.getClassLoader().getResource("");
Class.getResource和ClassLoader.getResource本質(zhì)上是一樣的,都是使用ClassLoader.getResource加載資源的。
對于Class.getResource:
先獲取文件的路徑path,不以’/‘開頭時(shí),默認(rèn)是從此類所在的包下取資源;path以’/'開頭時(shí),則是從項(xiàng)目的ClassPath根下獲取資源。
對于ClassLoader.getResource:
同樣先獲取文件的路徑,path不以’/'開頭時(shí),首先通過雙親委派機(jī)制,使用的逐級向上委托的形式加載的,最后發(fā)現(xiàn)雙親沒有加載到文件,最后通過當(dāng)前類加載classpath根下資源文件。
對于getResource("/"),’/'表示Boot ClassLoader中的加載范圍,因?yàn)檫@個(gè)類加載器是C++實(shí)現(xiàn)的,所以加載范圍為null。
2.以上兩種方法返回的都是 java.net.URL對象
如果需要得到相應(yīng)的String類型,可以用以下方法:
xxx.class.getResource("").getPath(); xxx.class.getResource("").getFile();
或者通過
InputStream input = getClass().getClassLoader().getResourceAsStream("config\\config.properties");
獲取IO流;
3.類加載器ClassLoader
我們都知道 Java 文件被運(yùn)行,第一步,需要通過 javac
編譯器編譯為 class 文件;第二步,JVM 運(yùn)行 class 文件,實(shí)現(xiàn)跨平臺(tái)。
而 JVM 虛擬機(jī)第一步肯定是 加載 class 文件,所以,類加載器實(shí)現(xiàn)的就是(來自《深入理解Java虛擬機(jī)》):
通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流
類加載器有幾個(gè)重要的特性:
- 每個(gè)類加載器都有自己的預(yù)定義的搜索范圍,用來加載 class 文件;
- 每個(gè)類和加載它的類加載器共同確定了這個(gè)類的唯一性,也就是說如果一個(gè) class 文件被不同的類加載器加載到了 JVM 中, 那么這兩個(gè)類就是不同的類,雖然他們都來自同一份 class 文件;
3.1 雙親委派模型
- 所有的類加載器都是有層級結(jié)構(gòu)的,每個(gè)類加載器都有一個(gè)父類類加載器(通過組合實(shí)現(xiàn),而不是繼承),除了啟動(dòng)類加載器(Bootstrap ClassLoader)
- 當(dāng)一個(gè)類加載器接收到一個(gè)類加載請求時(shí),首先將這個(gè)請求委派給它的父加載器去加載,所以每個(gè)類加載請求最終都會(huì)傳遞到頂層的啟動(dòng)類加載器,如果父加載器無法加載時(shí),子類加載器才會(huì)去嘗試自己去加載;
通過雙親委派模型就實(shí)現(xiàn)了類加載器的三個(gè)特性:
- 委派(delegation):子類加載器委派給父類加載器加載;
- 可見性(visibility):子類加載器可訪問父類加載器加載的類,父類不能訪問子類加載器加載的類;
- 唯一性(uniqueness):可保證每個(gè)類只被加載一次,比如
Object
類是被 Bootstrap ClassLoader 加載的,因?yàn)橛辛穗p親委派模型,所有的 Object 類加載請求都委派到了 Bootstrap ClassLoader,所以保證了只被加載一次。
3.2 Java 中的類加載器
從 JVM 虛擬機(jī)的角度來看,只存在兩種不同的類加載器:
- 啟動(dòng)類加載器(Bootstrap ClassLoader),是虛擬機(jī)自身的一部分;
- 所有其他的類加載器,獨(dú)立于虛擬機(jī)外部,都繼承自抽象類
java.lang.ClassLoader
而絕大多數(shù) Java 應(yīng)用都會(huì)用到如下 3 中系統(tǒng)提供的類加載器:
- 啟動(dòng)類加載器(Bootstrap/Primordial/NULL ClassLoader):頂層的類加載器,沒有父類加載器。負(fù)責(zé)加載 /lib 目錄下的,或則被 -Xbootclasspath 參數(shù)所指定路徑中的,
并被 JVM 識(shí)別的(僅按文件名識(shí)別,如 rt.jar,名字不符合的類庫即使放在 lib 目錄也不會(huì)被加載)類庫加載到虛擬機(jī)內(nèi)存中。所有被 Bootstrap classloader 加載的類,
它的 Class.getClassLoader 方法返回的都是 null,所以也稱作 NULL ClassLoader。
- 擴(kuò)展類加載器(Extension CLassLoader):由
sun.misc.Launcher$ExtClassLoader
實(shí)現(xiàn),負(fù)責(zé)加載<JAVA_HOME>/lib/ext
目錄下,或被java.ext.dirs
系統(tǒng)變量所指定的目錄下的所有類庫; - 應(yīng)用程序類加載器(Application/System ClassLoader):由
sun.misc.Launcher$AppClassLoader
實(shí)現(xiàn)。它是 ClassLoader.getSystemClassLoader() 方法的默認(rèn)返回值,
所以也稱為系統(tǒng)類加載器(System ClassLoader)。它負(fù)責(zé)加載 classpath 下所指定的類庫,如果應(yīng)用程序沒有自定義過自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
如下,就是 Java 程序中的類加載器層級結(jié)構(gòu)圖:
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringSecurity實(shí)現(xiàn)前后端分離登錄token認(rèn)證詳解
目前市面上比較流行的權(quán)限框架主要實(shí)Shiro和Spring Security,這兩個(gè)框架各自側(cè)重點(diǎn)不同,各有各的優(yōu)劣,本文將給大家詳細(xì)介紹SpringSecurity如何實(shí)現(xiàn)前后端分離登錄token認(rèn)證2023-06-06java8 集合 多字段 分組 統(tǒng)計(jì)個(gè)數(shù)代碼
這篇文章主要介紹了java8 集合 多字段 分組 統(tǒng)計(jì)個(gè)數(shù)代碼,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08如何將Java枚舉名稱作為注解的屬性值實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了如何將Java枚舉名稱作為注解的屬性值實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Java Optional解決空指針異常總結(jié)(java 8 功能)
這篇文章主要介紹了Java Optional解決空指針異??偨Y(jié)(java 8 功能),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11基于java Springboot實(shí)現(xiàn)教務(wù)管理系統(tǒng)詳解
這篇文章主要介紹了Java 實(shí)現(xiàn)簡易教務(wù)管理系統(tǒng)的代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08Java實(shí)現(xiàn)郵件發(fā)送遇到的問題
本文給大家分享的是個(gè)人在項(xiàng)目過程中,使用Java實(shí)現(xiàn)郵件發(fā)送的時(shí)候所遇到的幾個(gè)問題以及解決方法,有需要的小伙伴可以參考下2016-09-09Spring?Boot對接Oracle數(shù)據(jù)庫具體流程
這篇文章主要給大家介紹了關(guān)于Spring?Boot對接Oracle數(shù)據(jù)庫的具體流程,本文將介紹如何在Spring Boot中連接Oracle數(shù)據(jù)庫的基本配置,包括添加依賴、配置數(shù)據(jù)源、配置JPA等,需要的朋友可以參考下2023-11-11