項(xiàng)目打包成jar后包無法讀取src/main/resources下文件的解決
一、項(xiàng)目場景
在項(xiàng)目中讀取文件時(shí), 使用new File() 出現(xiàn)的一個(gè)坑以及解決流程
這種問題不僅在本地文件讀取時(shí)會(huì)遇到, 而且在下載項(xiàng)目下 (例如: src/main/resources目錄下) 的文本時(shí), 也會(huì)遇到,
二、問題描述
發(fā)現(xiàn)問題
原來代碼
該代碼功能是利用 common.io 包下的FileUtils來讀取文件, 放到一個(gè)字符串中
String s = FileUtils.readFileToString(new File("src/main/resources/holiday.txt"), "utf-8");這種路徑書寫方式 new File("src/main/resources/holiday.txt") , 在本地運(yùn)行沒問題,
但是打包之后在服務(wù)器中運(yùn)行出現(xiàn)了問題. 下面是錯(cuò)誤截圖

可以看到在服務(wù)器中日志提示: java.io.FileNotFoundException: File 'holiday.txt' does not exist
即: 在打包后, 一開始配置的路徑src/main/resources下無法找到該文件
分析問題
項(xiàng)目在打包之后, 位于 resource目錄下的文件, 最常見的就是各種Spring配置文件就會(huì)打包在 BOOT-INF/classes 目錄下
而FIle 在按照原來的文件路徑src/main/resources/holiday.txt'去尋找, 必然找不到文件, 因此會(huì)報(bào)文件找不到的異常

在定位問題的過程中發(fā)現(xiàn), 這里 提供了一個(gè)思路
就是SpringBoot中所有文件都在jar包中,沒有一個(gè)實(shí)際的路徑,因此可以使用以下方式
/**
* 通過ClassPathResource類獲取,建議SpringBoot中使用
* springboot項(xiàng)目中需要使用此種方法,因?yàn)閖ar包中沒有一個(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對象. 然后調(diào)用getInputStream來獲取資源文件
下面我們來分析這些代碼
在 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 方法, 我們來追一下這個(gè)方法

getResourceAsStream 方法中的getResource是實(shí)際的業(yè)務(wù)處理方法, 我們繼續(xù)深入

getResource 方法如下圖, 實(shí)際的功能就是遞歸調(diào)用自己, 去不斷遍歷 parent 下的路徑, 獲取對應(yīng)的資源文件
那么 parent 又是誰呢? 我們繼續(xù)往下看

看到這里我們豁然開朗, 這個(gè)神秘的 parent 就是類加載器classLoader!!!
因此getResource 方法就是去不斷遍歷我們在ClassPathResource實(shí)例化時(shí), 創(chuàng)建的類加載器下面的路徑!!!(對應(yīng)第1點(diǎn))

三、解決方案
原來讀取文件的代碼如下
String s = FileUtils.readFileToString(new File("src/main/resources/holiday.txt"), "utf-8");去查看 File 的構(gòu)造函數(shù), 看能否通過 InputStream 來構(gòu)造
從下圖看是不行的

方案一
并且我們發(fā)現(xiàn) org.apache.commons.io 下沒有提供將 ClassPathResource 作為入?yún)⒌淖x取文件的方法.
因此我們必須手寫讀取文件的方法

手寫的代碼如下
主要注意 Resource resource = new ClassPathResource(fileName); is = resource.getInputStream();
/**
* Java讀取txt文件的內(nèi)容
*
* @param fileName resources目錄下文件名稱(無需帶目錄)
* @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;
}
方案二
這種方式對代碼入侵較小, 核心還是利用 common.io 下的 FileUtils, 具體方法是
利用FileUtils將ClassPathResource.getInputStream 得到的輸入流復(fù)制到臨時(shí)文件中, 然后讀取這個(gè)臨時(shí)文件
這種方式缺點(diǎn)是: 需要?jiǎng)?chuàng)建臨時(shí)文件, 如果待讀取文件過大, 則重新創(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è)問題, 就是我用的測試項(xiàng)目因?yàn)樵?maven 里面指定了某些格式的文件. 如下配置
因?yàn)橹付薭anner.txt 以及 xml 與 properties結(jié)尾的文件作為資源被打包. 所以文件 holiday.txt 運(yù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 沒有被打包進(jìn)來

程序運(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)來 )

總結(jié)
在項(xiàng)目內(nèi)的文件的讀取/下載時(shí), 由于本地路徑和項(xiàng)目打包后的路徑不同. 出現(xiàn)找不到文件的情況,我們只需要例化ClassPathResource(文件名) 對象. 然后調(diào)用getInputStream 來獲取資源文件.就能獲取任意環(huán)境下項(xiàng)目內(nèi)的文件
如果想打算使用其他方式來獲取resources 目錄下的文件, 可以參見 這篇博客 .核心和上面問題分析差不多, 基本上都是通過類加載器來獲取資源文件的輸入流進(jìn)而找到這個(gè)文件
到此這篇關(guān)于項(xiàng)目打包成jar后包無法讀取src/main/resources下文件的解決的文章就介紹到這了,更多相關(guān)jar無法讀取src/main/resources文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java子類實(shí)例化總是默認(rèn)調(diào)用父類的無參構(gòu)造操作
這篇文章主要介紹了Java子類實(shí)例化總是默認(rèn)調(diào)用父類的無參構(gòu)造操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Java class文件格式之屬性詳解_動(dòng)力節(jié)點(diǎn)java學(xué)院整理
這篇文章主要介紹了Java class文件格式之屬性詳解,需要的朋友可以參考下2017-06-06
在SpringBoot中使用ResponseBodyAdvice自定義響應(yīng)的代碼實(shí)現(xiàn)
ResponseBodyAdvice是Spring Framework中的一個(gè)接口,允許您在將響應(yīng)寫入客戶端之前自定義響應(yīng),它通常與@ControllerAdvice注釋結(jié)合使用,以跨多個(gè)控制器將全局更改應(yīng)用于響應(yīng)主體,本文介紹了如何使用ResponseBodyAdvice的基本概述,需要的朋友可以參考下2024-12-12
基于Java SSM框架開發(fā)圖書借閱系統(tǒng)源代碼
本文給大家介紹了基于Java SSM框架開發(fā)圖書借閱系統(tǒng),開發(fā)環(huán)境基于idea2020+mysql數(shù)據(jù)庫,前端框架使用bootstrap4框架,完美了實(shí)現(xiàn)圖書借閱系統(tǒng),喜歡的朋友快來體驗(yàn)吧2021-05-05
SpringBoot中分頁插件PageHelper的使用詳解
分頁查詢是為了高效展示大量數(shù)據(jù),通過分頁將數(shù)據(jù)劃分為多個(gè)部分逐頁展示,原生方法需手動(dòng)計(jì)算數(shù)據(jù)起始行,而使用PageHelper插件則簡化這一過程,本文給大家介紹SpringBoot中分頁插件PageHelper的使用,感興趣的朋友一起看看吧2024-09-09
SpringBoot使用redis生成訂單號(hào)的實(shí)現(xiàn)示例
在電商系統(tǒng)中,生成唯一訂單號(hào)是常見需求,本文介紹如何利用SpringBoot和Redis實(shí)現(xiàn)訂單號(hào)的生成,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-09-09

