不同環境 (普 Java、Web、單元測試、打包 FatJar) 獲取路徑行為不一。本文彙總最穩健的通用寫法與工具類。
| 需求 | 推薦寫法 | 說明 |
|---|---|---|
| 讀取 classpath 資源 (輸入流) | Thread.currentThread().getContextClassLoader().getResourceAsStream("path") | Web/普 Java 通用,ContextClassLoader 通常指向應用類載入器 |
| 獲取 classpath 根目錄 URL | ThisClass.class.getResource("/") | 回傳 file:/.../classes/ 或 jar:file:/.../app.jar!/ |
| 獲取專案根目錄 (源碼層) | new File(ThisClass.class.getResource("/").toURI()).getParentFile().getParentFile() | 從 classes 向上兩層 (target/classes → target → project);**打包後失效** |
| 獲取當前 Jar 檔所在目錄 | new File(ThisClass.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParentFile() | FatJar/普 Java 部署通用 |
| Web 專案獲取 webapp 實體路徑 | servletContext.getRealPath("/") | 需注入 ServletContext |
public final class PathUtils {
private PathUtils() {}
// 獲取類載入器
private static ClassLoader cl() {
return Thread.currentThread().getContextClassLoader() != null ?
Thread.currentThread().getContextClassLoader() : PathUtils.class.getClassLoader();
}
// 讀取資源輸入流 (不拋檢查異常)
public static InputStream getResourceStream(String classpathPath) {
InputStream is = cl().getResourceAsStream(classpathPath);
if (is == null) throw new IllegalArgumentException("Classpath resource not found: " + classpathPath);
return is;
}
// 獲取 classpath 根目錄 File (僅展開檔案系統有效,Jar 內會拋異常)
public static File getClasspathRoot() {
try {
URL url = cl().getResource("");
return new File(url.toURI());
} catch (Exception e) {
throw new RuntimeException("Cannot resolve classpath root", e);
}
}
// 獲取應用部署根目錄 (優先外部配置、其次 Jar 目錄、最後 classpath 根上兩層)
public static File getAppRoot() {
String ext = System.getProperty("app.root"); // 啟動參數 -Dapp.root=/opt/app
if (ext != null) return new File(ext);
try {
URL url = PathUtils.class.getProtectionDomain().getCodeSource().getLocation();
File f = new File(url.toURI());
return f.isFile() ? f.getParentFile() : f; // Jar 則取父目錄,目錄則直接用
} catch (Exception e) {
return getClasspathRoot().getParentFile().getParentFile(); // 開發環境 fallback
}
}
}
getResourceAsStream,**絕不**轉 File 路徑。-Dapp.conf.dir=/etc/myapp、spring.config.location),**不要寫入 classpath/Jar 內部**。src/test/resources 同樣在 classpath,生產碼無感知。