項(xiàng)目打包成jar后包無(wú)法讀取src/main/resources下文件的解決
一、項(xiàng)目場(chǎng)景
在項(xiàng)目中讀取文件時(shí), 使用new File() 出現(xiàn)的一個(gè)坑以及解決流程
這種問(wèn)題不僅在本地文件讀取時(shí)會(huì)遇到, 而且在下載項(xiàng)目下 (例如: src/main/resources目錄下
) 的文本時(shí), 也會(huì)遇到,
二、問(wèn)題描述
發(fā)現(xiàn)問(wèn)題
原來(lái)代碼
該代碼功能是利用 common.io 包下的FileUtils來(lái)讀取文件, 放到一個(gè)字符串中
String s = FileUtils.readFileToString(new File("src/main/resources/holiday.txt"), "utf-8");
這種路徑書寫方式 new File("src/main/resources/holiday.txt")
, 在本地運(yùn)行沒(méi)問(wèn)題,
但是打包之后在服務(wù)器中運(yùn)行出現(xiàn)了問(wèn)題. 下面是錯(cuò)誤截圖
可以看到在服務(wù)器中日志提示: java.io.FileNotFoundException: File 'holiday.txt' does not exist
即: 在打包后, 一開(kāi)始配置的路徑src/main/resources
下無(wú)法找到該文件
分析問(wèn)題
項(xiàng)目在打包之后, 位于 resource目錄下的文件, 最常見(jiàn)的就是各種Spring配置文件就會(huì)打包在 BOOT-INF/classes
目錄下
而FIle 在按照原來(lái)的文件路徑src/main/resources/holiday.txt'
去尋找, 必然找不到文件, 因此會(huì)報(bào)文件找不到的異常
在定位問(wèn)題的過(guò)程中發(fā)現(xiàn), 這里 提供了一個(gè)思路
就是SpringBoot中所有文件都在jar包中,沒(méi)有一個(gè)實(shí)際的路徑,因此可以使用以下方式
/** * 通過(guò)ClassPathResource類獲取,建議SpringBoot中使用 * springboot項(xiàng)目中需要使用此種方法,因?yàn)閖ar包中沒(méi)有一個(gè)實(shí)際的路徑存放文件 * * @param fileName * @throws IOException */ public void function6(String fileName) throws IOException { ClassPathResource classPathResource = new ClassPathResource(fileName); InputStream inputStream = classPathResource.getInputStream(); getFileContent(inputStream); }
為什么使用 ClassPathResource 后, 可以找到打包后的文件路徑?
上面代碼的核心就是: 實(shí)例化
ClassPathResource
對(duì)象. 然后調(diào)用getInputStream
來(lái)獲取資源文件
下面我們來(lái)分析這些代碼
在 ClassPathResource
在實(shí)例化時(shí), 會(huì)初始化類加載器 classLoader
并將項(xiàng)目所用到的所有路徑加載到類加載器 classLoader
中, 這些路徑包括: java運(yùn)行環(huán)境的jar, Maven 項(xiàng)目中的jar, 以及當(dāng)前項(xiàng)目打包后的jar等(如下圖)
而 classPathResource.getInputStream
在獲取資源文件時(shí), 因?yàn)樯厦嫖覀兂跏蓟艘粋€(gè)classLoader
.
所以classLoader
不為空, 因此會(huì)執(zhí)行 getResourceAsStream
方法, 我們來(lái)追一下這個(gè)方法
getResourceAsStream
方法中的getResource
是實(shí)際的業(yè)務(wù)處理方法, 我們繼續(xù)深入
getResource
方法如下圖, 實(shí)際的功能就是遞歸調(diào)用自己, 去不斷遍歷 parent
下的路徑, 獲取對(duì)應(yīng)的資源文件
那么 parent
又是誰(shuí)呢? 我們繼續(xù)往下看
看到這里我們豁然開(kāi)朗, 這個(gè)神秘的 parent
就是類加載器classLoader
!!!
因此getResource
方法就是去不斷遍歷我們?cè)?/strong>ClassPathResource
實(shí)例化時(shí), 創(chuàng)建的類加載器下面的路徑!!!(對(duì)應(yīng)第1點(diǎn))
三、解決方案
原來(lái)讀取文件的代碼如下
String s = FileUtils.readFileToString(new File("src/main/resources/holiday.txt"), "utf-8");
去查看 File 的構(gòu)造函數(shù), 看能否通過(guò) InputStream
來(lái)構(gòu)造
從下圖看是不行的
方案一
并且我們發(fā)現(xiàn) org.apache.commons.io
下沒(méi)有提供將 ClassPathResource
作為入?yún)⒌淖x取文件的方法.
因此我們必須手寫讀取文件的方法
手寫的代碼如下
主要注意 Resource resource = new ClassPathResource(fileName); is = resource.getInputStream();
/** * Java讀取txt文件的內(nèi)容 * * @param fileName resources目錄下文件名稱(無(wú)需帶目錄) * @return 將每行作為一個(gè)單位放到list中 */ public static List<String> readTxtFile(String fileName) { List<String> listContent = new ArrayList<>(); InputStream is = null; InputStreamReader isr = null; BufferedReader br = null; String encoding = "utf-8"; try { Resource resource = new ClassPathResource(fileName); is = resource.getInputStream(); isr = new InputStreamReader(is, encoding); br = new BufferedReader(isr); String lineTxt = null; while ((lineTxt = br.readLine()) != null) { listContent.add(lineTxt); } } catch (IOException e) { e.printStackTrace(); } finally { try { br.close(); isr.close(); is.close(); } catch (IOException e) { e.printStackTrace(); } } return listContent; }
方案二
這種方式對(duì)代碼入侵較小, 核心還是利用 common.io 下的 FileUtils, 具體方法是
利用FileUtils將ClassPathResource.getInputStream
得到的輸入流復(fù)制到臨時(shí)文件中, 然后讀取這個(gè)臨時(shí)文件
這種方式缺點(diǎn)是: 需要?jiǎng)?chuàng)建臨時(shí)文件, 如果待讀取文件過(guò)大, 則重新創(chuàng)建文件和復(fù)制操作會(huì)消耗一定的空間和時(shí)間, 影響性能
//方式二 利用FileUtils將ClassPathResource.getInputStream 得到的輸入流復(fù)制到臨時(shí)文件中 Resource resource = new ClassPathResource("holiday.txt"); InputStream inputStream = resource.getInputStream(); File tempFile = File.createTempFile("temp", ".txt"); FileUtils.copyInputStreamToFile(inputStream, tempFile); String s = FileUtils.readFileToString(tempFile, StandardCharsets.UTF_8);
意外出現(xiàn)
到這里又出現(xiàn)了一個(gè)問(wèn)題, 就是我用的測(cè)試項(xiàng)目因?yàn)樵?maven 里面指定了某些格式的文件. 如下配置
因?yàn)橹付薭anner.txt 以及 xml 與 properties結(jié)尾的文件作為資源被打包. 所以文件 holiday.txt 運(yùn)行后還是訪問(wèn)不到
有問(wèn)題的pom.xml文件如下
<!-- 資源拷貝插件,實(shí)現(xiàn)在打包時(shí)自動(dòng)拷貝java目錄下以及resources目錄下的xml的配置文件 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> <include>**/banner.txt</include> </includes> </resource> </resources>
打包后資源文件截圖如下, 從該圖中可以看到 holiday.txt 沒(méi)有被打包進(jìn)來(lái)
程序運(yùn)行之后的錯(cuò)誤截圖
我們修改下指定打包的配置 <include>**/*.txt</include>
這樣配置后, 我們就可以將類路徑下的所有txt 文件打包進(jìn)行項(xiàng)目中了, 打包之后文件位置如下圖
或者我們可以去除項(xiàng)目中下面的代碼配置, 這樣做會(huì)默認(rèn)打包 resources 下面的所有文件
<!-- 資源拷貝插件,實(shí)現(xiàn)在打包時(shí)自動(dòng)拷貝java目錄下以及resources目錄下的xml的配置文件 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> <include>**/*.txt</include> </includes> </resource> </resources>
修改pom文件后, 重新打包后資源文件(從這里可以看到 holiday.txt 被打包進(jìn)來(lái) )
總結(jié)
在項(xiàng)目?jī)?nèi)的文件的讀取/下載時(shí), 由于本地路徑和項(xiàng)目打包后的路徑不同. 出現(xiàn)找不到文件的情況,我們只需要例化ClassPathResource(文件名)
對(duì)象. 然后調(diào)用getInputStream 來(lái)獲取資源文件.就能獲取任意環(huán)境下項(xiàng)目?jī)?nèi)的文件
如果想打算使用其他方式來(lái)獲取resources 目錄下的文件, 可以參見(jiàn) 這篇博客 .核心和上面問(wèn)題分析差不多, 基本上都是通過(guò)類加載器來(lái)獲取資源文件的輸入流進(jìn)而找到這個(gè)文件
到此這篇關(guān)于項(xiàng)目打包成jar后包無(wú)法讀取src/main/resources下文件的解決的文章就介紹到這了,更多相關(guān)jar無(wú)法讀取src/main/resources文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot Druid 自定義加密數(shù)據(jù)庫(kù)密碼的幾種方案
這篇文章主要介紹了Springboot Druid 自定義加密數(shù)據(jù)庫(kù)密碼的步驟,幫助大家更好的理解和使用springboot,感興趣的朋友可以了解下2020-12-12如何基于Jenkins構(gòu)建Jmeter項(xiàng)目
這篇文章主要介紹了如何基于Jenkins構(gòu)建Jmeter項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Spring Boot整合JWT的實(shí)現(xiàn)步驟
本文主要介紹了Spring Boot整合JWT,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Spring Boot項(xiàng)目利用Redis實(shí)現(xiàn)session管理實(shí)例
本篇文章主要介紹了Spring Boot項(xiàng)目利用Redis實(shí)現(xiàn)session管理實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06Java紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu)與算法解析
紅黑樹(shù)問(wèn)題是各大計(jì)算機(jī)考研命題以及面試算法題目中的熱門,接下來(lái)我們?yōu)榇蠹覉D解紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu)與算法解析,需要的朋友可以參考下2021-08-08spring中的@Value讀取配置文件的細(xì)節(jié)處理過(guò)程
這篇文章主要介紹了spring中的@Value讀取配置文件的細(xì)節(jié)處理過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09一文詳解SpringBoot如何優(yōu)雅地實(shí)現(xiàn)異步調(diào)用
SpringBoot想必大家都用過(guò),但是大家平時(shí)使用發(fā)布的接口大都是同步的,那么你知道如何優(yōu)雅的實(shí)現(xiàn)異步呢?這篇文章就來(lái)和大家詳細(xì)聊聊2023-03-03