項目打包成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")
, 在本地運(yùn)行沒問題,
但是打包之后在服務(wù)器中運(yùn)行出現(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運(yùn)行環(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 運(yùn)行后還是訪問不到
有問題的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 沒有被打包進(jìn)來
程序運(yùn)行之后的錯誤截圖
我們修改下指定打包的配置 <include>**/*.txt</include>
這樣配置后, 我們就可以將類路徑下的所有txt 文件打包進(jìn)行項目中了, 打包之后文件位置如下圖
或者我們可以去除項目中下面的代碼配置, 這樣做會默認(rèn)打包 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 被打包進(jìn)來 )
總結(jié)
在項目內(nèi)的文件的讀取/下載時, 由于本地路徑和項目打包后的路徑不同. 出現(xiàn)找不到文件的情況,我們只需要例化ClassPathResource(文件名)
對象. 然后調(diào)用getInputStream 來獲取資源文件.就能獲取任意環(huán)境下項目內(nèi)的文件
如果想打算使用其他方式來獲取resources 目錄下的文件, 可以參見 這篇博客 .核心和上面問題分析差不多, 基本上都是通過類加載器來獲取資源文件的輸入流進(jìn)而找到這個文件
到此這篇關(guān)于項目打包成jar后包無法讀取src/main/resources下文件的解決的文章就介紹到這了,更多相關(guān)jar無法讀取src/main/resources文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot Druid 自定義加密數(shù)據(jù)庫密碼的幾種方案
這篇文章主要介紹了Springboot Druid 自定義加密數(shù)據(jù)庫密碼的步驟,幫助大家更好的理解和使用springboot,感興趣的朋友可以了解下2020-12-12Spring Boot項目利用Redis實現(xiàn)session管理實例
本篇文章主要介紹了Spring Boot項目利用Redis實現(xiàn)session管理實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06Java紅黑樹的數(shù)據(jù)結(jié)構(gòu)與算法解析
紅黑樹問題是各大計算機(jī)考研命題以及面試算法題目中的熱門,接下來我們?yōu)榇蠹覉D解紅黑樹的數(shù)據(jù)結(jié)構(gòu)與算法解析,需要的朋友可以參考下2021-08-08spring中的@Value讀取配置文件的細(xì)節(jié)處理過程
這篇文章主要介紹了spring中的@Value讀取配置文件的細(xì)節(jié)處理過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09一文詳解SpringBoot如何優(yōu)雅地實現(xiàn)異步調(diào)用
SpringBoot想必大家都用過,但是大家平時使用發(fā)布的接口大都是同步的,那么你知道如何優(yōu)雅的實現(xiàn)異步呢?這篇文章就來和大家詳細(xì)聊聊2023-03-03