解析Java和IDEA中的文件打包問題
問題:想在IDEA中引用相對路徑,但是找不到文件。
項目目錄結(jié)構(gòu)
當(dāng)前項目的路徑為:D:\source\java\test\
項目結(jié)構(gòu)如下
當(dāng)前路徑
面對無法使用相對路徑找到資源文件的問題,首先想到的解決辦法是先瞄一眼IDEA在執(zhí)行時給Java環(huán)境設(shè)定的當(dāng)前路徑在哪,也就是說看看我們在使用相對路徑時到底是相對于哪里的。
應(yīng)該咋寫呢?下面是Java API中的一些描述。
默認(rèn)情況下, java.io包中的類始終會根據(jù)當(dāng)前用戶目錄解析相對路徑名。 該目錄由系統(tǒng)屬性user.dir ,通常是調(diào)用Java虛擬機的目錄。
根據(jù)上面的信息,可以想到兩個辦法來獲得當(dāng)前路徑,第一種辦法是向Java中傳遞空字符串,這是一個比較hack的方法,按照相對路徑來解析的話,自然會被解析成當(dāng)前目錄。
File f1 = new File(""); // 空字符串,相當(dāng)于當(dāng)前路徑 System.out.println(f1.getAbsolutePath());
第二種辦法,直接獲取系統(tǒng)屬性中的user.dir
。
System.out.println(System.getProperty("user.dir"));
兩種辦法得到的結(jié)果一致:
也就是說,當(dāng)前目錄指向項目的根目錄,如果你想要獲取src
中的一個文件的話,需要使用src/文件名
,而如果該文件在某個包下的話,則需要使用src/完整包路徑/文件名
。
現(xiàn)在我在src下創(chuàng)建一個文件text1.txt
,寫入內(nèi)容HELLO , WORLD
File f2 = new File("src/text1.txt"); LineNumberReader reader2 = new LineNumberReader(new FileReader(f2)); System.out.println(reader2.readLine());
結(jié)果
這樣寫好嗎?
我們的Java代碼最終會被打包上傳到目標(biāo)平臺,如某個服務(wù)器上,打包的代碼應(yīng)該只包含編譯后的文件目錄,在IDEA中就是那個out
目錄,其它的文件都不會出現(xiàn)在目標(biāo)平臺上,到那個時候,你不再擁有當(dāng)前項目編寫時的目錄結(jié)構(gòu),你的程序運行時所在的當(dāng)前路徑也不再是現(xiàn)在這個。
簡單來說,我們寫出了依賴環(huán)境的代碼,這個代碼不保證程序運行在生產(chǎn)環(huán)境下的時候還可以正常執(zhí)行,而且通常是無法正常執(zhí)行。
唯一的辦法,就是我們在IDEA編譯時,告訴它有些文件,請你幫我打包到out
目錄中,然后我再想辦法去out
目錄中找,這下就不會出問題了。
JavaAPI中有這樣一個方法,它被明確說明是用來干這個事的就是ClassLoader
中的getResource
,下面是API中的說明
尋找給定名字的資源文件。一個資源文件可以是一些能夠被class代碼以一種獨立于代碼位置的方式進(jìn)行訪問的數(shù)據(jù)(圖像、聲音、文字等)
我們先看看使用這個方法是相對于哪個路徑,采用的辦法和new File("")
大同小異。
String path = Main.class.getClassLoader().getResource("").getPath(); System.out.println(path);
結(jié)果如下
它已經(jīng)找到了我們打包后的二進(jìn)制代碼所在的位置,這下就可以相對于二進(jìn)制代碼的位置讀寫文件了,不再依賴當(dāng)前環(huán)境。
注意,ClassLoader.getResourceAsStream和Class.getResourceAsStream有些細(xì)微的區(qū)別,見JAVA 筆記 ClassLoader.getResourceAsStream() 與 Class.getResourceAsStream()的區(qū)別
將文件打包到二進(jìn)制代碼位置
創(chuàng)建文件夾,mark directory as -> Resources Root
。
o
在其中創(chuàng)建text2.txt
,寫入HELLO , RESOURCES
。
然后這樣去讀
LineNumberReader r3 = new LineNumberReader( new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("text2.txt")) ); System.out.println(r3.readLine());
這里使用了ClassLoader.getResource
的一個變體getResourceAsStream
,它的作用就是返回的是一個流,而非URL
。我們所做的就是獲取當(dāng)前類的ClassLoader,通過它獲取資源文件,轉(zhuǎn)換成LineNumberReader。
讀取成功,也就是說,在resources
文件夾下的文件,IDEA會自動幫我們打包到二進(jìn)制代碼的位置
其他
src中的文件
如果我們?nèi)タ?code>out文件夾下的文件結(jié)構(gòu),你就會發(fā)現(xiàn),除了resources/text2.txt
,我們之前在src
下創(chuàng)建的text1.txt
也被打包到這個位置了。也就是說,我們可以通過同樣的方式去讀取src
下的文件。
D:\source\java\test\out>wsl tree . . └── production └── test ├── io │ └── lilpig │ └── test │ └── Main.class ├── text1.txt └── text2.txt 5 directories, 3 files LineNumberReader r4 = new LineNumberReader( new InputStreamReader(Main.class.getClassLoader().getResourceAsStream("text1.txt")) ); System.out.println(r4.readLine());
讀取成功
Thread.currentThread.getContextClassLoader
在多線程應(yīng)用中,有可能你編寫的類的類加載器和實際加載resources
文件夾的類加載器不是同一個,這會導(dǎo)致你的代碼無法獲取到資源文件,使用Thread.currentThread.getContextClassLoader
能規(guī)避這個問題。具體的原理涉及到Java中的類加載機制的委托模型,這部分的內(nèi)容原理性太強,我已經(jīng)忘得差不多了,就不露怯了。
我們編寫的大部分后端應(yīng)用,都是多線程應(yīng)用,我們自己感知不到,那是因為多線程代碼被封裝在框架中,所以無論如何,使用Thread.currentThread.getContextClassLoader
總比使用Main.class.getClassLoader
更好。
并且,Thread.currentThread.getContextClassLoader
更加具有一致性,你可以輕易的編寫一個工具方法來簡化代碼(無論如何Thread.currentThread返回的總是當(dāng)前調(diào)用者所在的線程)。而如果用之前的方法,每一個類的類名都不同,你在一個類中寫的是Main.class.getClassLoader
在另一個類中寫的又是Other.class.getClassLoader
,這不一致的代碼會讓我們難以編寫一個通用的工具類。
框架
在框架中,資源文件夾都是默認(rèn)被創(chuàng)建好的,有的可能叫static
,有的可能叫resources
......而且,有可能編譯后的目錄不是out
,而是target
,甚至?xí)淮虬?code>war包等。
我們要做的不是管它打包后在哪,而是直接往框架給你指定好的資源文件夾里面放文件就行,剩下的交給框架。
并且在框架中,隨處可見Thread.currentThread.getClassLoader().getResource...
這樣的代碼。
參考
Thread.currentThread().getContextClassLoader() 和 Class.getClassLoader()區(qū)別
到此這篇關(guān)于Java和IDEA中的文件打包的文章就介紹到這了,更多相關(guān)Java和IDEA中的文件打包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis-Plus實現(xiàn)多數(shù)據(jù)源的示例代碼
這篇文章主要介紹了MyBatis-Plus實現(xiàn)多數(shù)據(jù)源的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11java新特性之for循環(huán)最全的用法總結(jié)
下面小編就為大家?guī)硪黄猨ava新特性之for循環(huán)最全的用法總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12IntelliJ IDEA 好用插件之a(chǎn)nalyze inspect code詳解
這篇文章主要介紹了IntelliJ IDEA 好用插件之a(chǎn)nalyze inspect code的相關(guān)知識,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2020-12-12聊聊@RequestBody和Json之間的關(guān)系
這篇文章主要介紹了@RequestBody和Json之間的關(guān)系,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06Java實現(xiàn)樹形List與扁平List互轉(zhuǎn)的示例代碼
在平時的開發(fā)中,我們時常會遇到需要將"樹形List"與"扁平List"互轉(zhuǎn)的情況,本文為大家整理了Java實現(xiàn)樹形List與扁平List互轉(zhuǎn)的示例代碼,希望對大家有所幫助2023-05-05Java內(nèi)存模型(JMM)及happens-before原理
這篇文章主要介紹了java內(nèi)存模型(JMM)及happens-before原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04