項(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-09
Spring Boot整合JWT的實(shí)現(xiàn)步驟
本文主要介紹了Spring Boot整合JWT,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Spring Boot項(xiàng)目利用Redis實(shí)現(xiàn)session管理實(shí)例
本篇文章主要介紹了Spring Boot項(xiàng)目利用Redis實(shí)現(xiàn)session管理實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06
Java紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu)與算法解析
紅黑樹(shù)問(wèn)題是各大計(jì)算機(jī)考研命題以及面試算法題目中的熱門,接下來(lái)我們?yōu)榇蠹覉D解紅黑樹(shù)的數(shù)據(jù)結(jié)構(gòu)與算法解析,需要的朋友可以參考下2021-08-08
spring中的@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

