SpringBoot打包后讀取JAR包同級(jí)目錄的外部文件的方法匯總
背景與痛點(diǎn)
在平時(shí)的項(xiàng)目開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到這樣的需求:項(xiàng)目中有一個(gè) template 文件夾(比如存放 Word、Excel 模板,或者 Freemarker/HTML 模板),我們希望在代碼中去讀取它。
如果是直接放在 src/main/resources 下,打包后文件會(huì)被打進(jìn) JAR 包內(nèi)部。這就帶來(lái)一個(gè)問(wèn)題:如果生產(chǎn)環(huán)境下需要修改模板內(nèi)容,就必須重新打包發(fā)布,非常不方便。
因此,更優(yōu)雅的做法是:將 template 文件夾放在項(xiàng)目根目錄,并在打包后,讓它與 JAR 包處于同級(jí)目錄。代碼去讀取這個(gè)外部文件夾。
本文將手把手教你如何實(shí)現(xiàn)這個(gè)需求,并避開(kāi)路徑解析中的常見(jiàn)“坑”。
避坑指南:為什么不建議直接用./template?
很多開(kāi)發(fā)者直覺(jué)上會(huì)寫(xiě)出這樣的代碼:
File templateDir = new File("./template");
結(jié)論:能用,但有非常大的隱患!
在 Java 中,./ 代表的是當(dāng)前工作目錄(Current Working Directory),等同于 System.getProperty("user.dir")。它的致命弱點(diǎn)在于:它依賴(lài)于“你在哪里敲的啟動(dòng)命令”,而不是“JAR 包在哪里”。
- 場(chǎng)景 1(正常): 你
cd到了 JAR 包所在的目錄,然后執(zhí)行java -jar app.jar。此時(shí)./就是 JAR 包所在目錄,讀取成功。 - 場(chǎng)景 2(報(bào)錯(cuò)): 你的 JAR 包在
/app/my-project/下,但你在系統(tǒng)根目錄啟動(dòng)項(xiàng)目:
cd / java -jar /app/my-project/app.jar
此時(shí),Java 認(rèn)為的 ./ 是根目錄 /!程序會(huì)去尋找 /template,結(jié)果自然是找不到文件。
- 場(chǎng)景 3(報(bào)錯(cuò)): 將 Spring Boot 配置為 Linux 的 Systemd 服務(wù)啟動(dòng)時(shí),如果沒(méi)有嚴(yán)格配置
WorkingDirectory,默認(rèn)路徑也會(huì)導(dǎo)致找不到文件。
為了代碼的健壯性,我們需要使用絕對(duì)路徑來(lái)定位 JAR 包!
核心方案:精準(zhǔn)定位 JAR 包所在目錄
方法 1:Spring Boot 的極簡(jiǎn)寫(xiě)法(強(qiáng)烈推薦)
如果你使用的是 Spring Boot 項(xiàng)目,Spring 官方提供了一個(gè)非常貼心的類(lèi) ApplicationHome。它可以自動(dòng)判斷當(dāng)前是處于 IDE 環(huán)境還是 JAR 包運(yùn)行環(huán)境,并牢牢鎖定真實(shí)的根目錄。
import org.springframework.boot.system.ApplicationHome;
import java.io.File;
public class TemplateReader {
public void readTemplates() {
// 獲取當(dāng)前程序所在的根目錄 (IDE中是項(xiàng)目根目錄,打包后是 jar 包所在目錄)
ApplicationHome home = new ApplicationHome(getClass());
File rootDir = home.getDir();
// 拼接出 template 目錄的絕對(duì)路徑
File templateDir = new File(rootDir, "template");
if (templateDir.exists() && templateDir.isDirectory()) {
System.out.println("成功找到 template 目錄: " + templateDir.getAbsolutePath());
File[] files = templateDir.listFiles();
if (files != null) {
for (File file : files) {
System.out.println("讀取到文件: " + file.getName());
// 在這里處理你的文件邏輯...
}
}
} else {
System.out.println("未找到 template 目錄,請(qǐng)檢查路徑: " + templateDir.getAbsolutePath());
}
}
}方法 2:原生 Java 通用寫(xiě)法(適用于非 Spring 項(xiàng)目)
如果你使用的是普通的 Java Maven 項(xiàng)目,可以通過(guò) ProtectionDomain 獲取當(dāng)前類(lèi)的實(shí)際運(yùn)行物理路徑,然后反推根目錄:
import java.io.File;
import java.net.URLDecoder;
public class NativeTemplateReader {
private static File getTemplateDir() {
try {
// 獲取當(dāng)前類(lèi)的代碼源路徑
String path = NativeTemplateReader.class.getProtectionDomain().getCodeSource().getLocation().getPath();
path = URLDecoder.decode(path, "UTF-8"); // 防止路徑中的中文或空格被 URLEncode
File file = new File(path);
File rootDir;
if (file.isFile() && path.endsWith(".jar")) {
// 環(huán)境:打包后的 jar 運(yùn)行,根目錄就是 jar 包所在的父目錄
rootDir = file.getParentFile();
} else {
// 環(huán)境:IDE (如 IDEA) 中運(yùn)行,退回項(xiàng)目根目錄
rootDir = new File(System.getProperty("user.dir"));
}
return new File(rootDir, "template");
} catch (Exception e) {
// 兜底方案
return new File(System.getProperty("user.dir"), "template");
}
}
}
工程化進(jìn)階:讓 Maven 打包時(shí)自動(dòng)復(fù)制文件夾
代碼寫(xiě)好了,但每次執(zhí)行 mvn clean package 打包后,我們還需要手動(dòng)把根目錄的 template 文件夾復(fù)制到 target 目錄下,和 jar 包放在一起,太繁瑣了!
我們可以通過(guò)配置 maven-resources-plugin 插件,讓 Maven 在打包時(shí)自動(dòng)完成復(fù)制。
1. 規(guī)范的項(xiàng)目結(jié)構(gòu)
首先確保你的項(xiàng)目目錄長(zhǎng)這樣:
my-project/ ├── src/ │ └── main/java/... ├── template/ <-- 放在項(xiàng)目根目錄下,與 src 同級(jí) │ ├── test1.txt │ └── test2.html └── pom.xml
2. 修改 pom.xml
在 <build><plugins> 節(jié)點(diǎn)下添加以下配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>copy-template-folder</id>
<!-- 在打包(package)階段執(zhí)行 -->
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<!-- 復(fù)制的目標(biāo)位置:打包后的 target 目錄下的 template 文件夾 -->
<outputDirectory>${project.build.directory}/template</outputDirectory>
<resources>
<resource>
<!-- 復(fù)制的源位置:項(xiàng)目根目錄下的 template 文件夾 -->
<directory>${project.basedir}/template</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>最終效果驗(yàn)證
- 開(kāi)發(fā)階段:直接在 IDEA 中運(yùn)行,代碼會(huì)精準(zhǔn)讀取項(xiàng)目根目錄下的
template文件夾。 - 打包階段:執(zhí)行
mvn clean package。你會(huì)發(fā)現(xiàn)target目錄下不僅有xxx.jar,旁邊還自動(dòng)多出了一個(gè)template文件夾。 - 部署階段:把
target目錄下的 jar 包和template文件夾一起丟到服務(wù)器上。無(wú)論你用什么姿勢(shì)(跨目錄、寫(xiě)腳本、systemd)啟動(dòng)項(xiàng)目,代碼都能穩(wěn)穩(wěn)地讀取到外部的模板文件。 - 后期運(yùn)維:需要改模板?直接替換服務(wù)器上
template里的文件即可,無(wú)需重啟!無(wú)需重新打包!
總結(jié)
- 讀取外部同級(jí)文件,不要直接用
./,容易因?yàn)閱?dòng)目錄不同而產(chǎn)生 BUG。 - Spring Boot 項(xiàng)目無(wú)腦使用
ApplicationHome。 - 結(jié)合 Maven
maven-resources-plugin插件,可以實(shí)現(xiàn)絲滑的自動(dòng)化打包體驗(yàn)。
以上就是SpringBoot打包后讀取JAR包同級(jí)目錄的外部文件的方法匯總的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot打包后讀取JAR包外部文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring-webflux?響應(yīng)式編程的實(shí)例詳解
Spring 提供了兩個(gè)并行堆棧,一種是基于帶有 Spring MVC 和 Spring Data 結(jié)構(gòu)的 Servlet API,另一個(gè)是完全反應(yīng)式堆棧,它利用了 Spring WebFlux 和 Spring Data 的反應(yīng)式存儲(chǔ)庫(kù),這篇文章主要介紹了Spring-webflux?響應(yīng)式編程,需要的朋友可以參考下2022-09-09
java開(kāi)發(fā)RocketMQ生產(chǎn)者高可用示例詳解
這篇文章主要為大家介紹了java開(kāi)發(fā)RocketMQ生產(chǎn)者高可用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
SpringBoot多模塊依賴(lài)沖突排查與架構(gòu)優(yōu)化實(shí)戰(zhàn)
本文詳細(xì)記錄了在SpringBoot多模塊項(xiàng)目開(kāi)發(fā)中遇到的依賴(lài)版本沖突、模塊依賴(lài)設(shè)計(jì)不合理等問(wèn)題的排查和解決方案的過(guò)程,主要重點(diǎn)關(guān)注了父模塊依賴(lài)管理、模塊化拆分、按需依賴(lài)等等,需要的朋友可以參考下2026-04-04
springMVC實(shí)現(xiàn)前臺(tái)帶進(jìn)度條文件上傳的示例代碼
本篇文章主要介紹了springMVC實(shí)現(xiàn)前臺(tái)帶進(jìn)度條文件上傳的示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
Java五子棋簡(jiǎn)單實(shí)現(xiàn)代碼舉例
Java五子棋游戲是一種經(jīng)典的兩人對(duì)戰(zhàn)棋類(lèi)游戲,它基于簡(jiǎn)單的規(guī)則,即任何一方的棋子在棋盤(pán)上形成連續(xù)的五個(gè),無(wú)論是橫、豎還是斜線(xiàn),都將獲勝,這篇文章主要介紹了Java五子棋實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-10-10
Java中定時(shí)任務(wù)的全方位場(chǎng)景實(shí)現(xiàn)思路分析
在開(kāi)發(fā)過(guò)程中,根據(jù)需求和業(yè)務(wù)的不同經(jīng)常會(huì)有很多場(chǎng)景需要用到不同特性的定時(shí)任務(wù),本文將針對(duì)這些場(chǎng)景,提供不同的一個(gè)實(shí)現(xiàn)思路,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧2023-12-12
mybatis-plus多表關(guān)聯(lián)查詢(xún)功能的實(shí)現(xiàn)
本文給大家介紹mybatis-plus多表關(guān)聯(lián)查詢(xún)功能的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-11-11
Java數(shù)據(jù)結(jié)構(gòu)之循環(huán)隊(duì)列簡(jiǎn)單定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之循環(huán)隊(duì)列簡(jiǎn)單定義與用法,簡(jiǎn)要描述了循環(huán)隊(duì)列的概念、原理,并結(jié)合實(shí)例形式分析了java循環(huán)隊(duì)列的定義與使用方法,需要的朋友可以參考下2017-10-10
java使用udp實(shí)現(xiàn)簡(jiǎn)單多人聊天功能
這篇文章主要為大家詳細(xì)介紹了java使用udp實(shí)現(xiàn)簡(jiǎn)單多人聊天功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
Hadoop?MapReduce實(shí)現(xiàn)單詞計(jì)數(shù)(Word?Count)
這篇文章主要為大家詳細(xì)介紹了如何利用Hadoop實(shí)現(xiàn)單詞計(jì)數(shù)(Word?Count)的MapReduce,文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起學(xué)習(xí)一下2023-05-05

