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

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

在定位問題的過程中發(fā)現(xiàn), 這里 提供了一個思路
就是SpringBoot中所有文件都在jar包中,沒有一個實際的路徑,因此可以使用以下方式
/**
* 通過ClassPathResource類獲取,建議SpringBoot中使用
* springboot項目中需要使用此種方法,因為jar包中沒有一個實際的路徑存放文件
*
* @param fileName
* @throws IOException
*/
public void function6(String fileName) throws IOException {
ClassPathResource classPathResource = new ClassPathResource(fileName);
InputStream inputStream = classPathResource.getInputStream();
getFileContent(inputStream);
}
為什么使用 ClassPathResource 后, 可以找到打包后的文件路徑?
上面代碼的核心就是: 實例化
ClassPathResource對象. 然后調(diào)用getInputStream來獲取資源文件
下面我們來分析這些代碼
在 ClassPathResource 在實例化時, 會初始化類加載器 classLoader 并將項目所用到的所有路徑加載到類加載器 classLoader 中, 這些路徑包括: java運行環(huán)境的jar, Maven 項目中的jar, 以及當(dāng)前項目打包后的jar等(如下圖)

而 classPathResource.getInputStream 在獲取資源文件時, 因為上面我們初始化了一個classLoader.
所以classLoader不為空, 因此會執(zhí)行 getResourceAsStream 方法, 我們來追一下這個方法

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

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

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

三、解決方案
原來讀取文件的代碼如下
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 將每行作為一個單位放到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ù)制到臨時文件中, 然后讀取這個臨時文件
這種方式缺點是: 需要創(chuàng)建臨時文件, 如果待讀取文件過大, 則重新創(chuàng)建文件和復(fù)制操作會消耗一定的空間和時間, 影響性能
//方式二 利用FileUtils將ClassPathResource.getInputStream 得到的輸入流復(fù)制到臨時文件中
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)了一個問題, 就是我用的測試項目因為在 maven 里面指定了某些格式的文件. 如下配置
因為指定了banner.txt 以及 xml 與 properties結(jié)尾的文件作為資源被打包. 所以文件 holiday.txt 運行后還是訪問不到
有問題的pom.xml文件如下
<!-- 資源拷貝插件,實現(xiàn)在打包時自動拷貝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 沒有被打包進來

程序運行之后的錯誤截圖

我們修改下指定打包的配置 <include>**/*.txt</include>
這樣配置后, 我們就可以將類路徑下的所有txt 文件打包進行項目中了, 打包之后文件位置如下圖
或者我們可以去除項目中下面的代碼配置, 這樣做會默認打包 resources 下面的所有文件
<!-- 資源拷貝插件,實現(xiàn)在打包時自動拷貝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 被打包進來 )

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

