路徑分類與核心概念
| 分類 | 關鍵特徵 | 典型 API | 適用場景 |
| 文件系統絕對路徑 | 磁盤真實路徑,如 D:/data/upload/ | File.getAbsolutePath() | 文件讀寫、日誌、外部存儲 |
| Web 應用根路徑 | Webapp 部署根目錄 (含 WEB-INF、靜態資源) | ServletContext.getRealPath("/") | 上傳文件存放、模板文件讀取 |
| 類路徑 | ClassLoader 可見資源根 (classes/、lib/*.jar) | Class.getResource("") | 配置文件、國際化資源、類路徑下腳本 |
| Web 相對路徑 | 相對於 Web 根的 URL 路徑 | request.getContextPath() | 頁面跳轉、JS/CSS 引用、API 路徑拼接 |
非 Web 環境 (Main 方法、單元測試、定時任務)
public class PathUtils {
/**
* 獲取類編譯輸出目錄 (target/classes 或構建輸出目錄)
* Spring Boot 打包為 JAR 時返回 JAR 內部路徑 (jar:file:...!/),不可直接 new File()
*/
public static String getClassOutputPath(Class> clazz) {
String path = clazz.getResource("").getPath(); // file:/.../target/classes/com/...
return URLDecoder.decode(path, StandardCharsets.UTF_8);
}
/** 獲取項目根目錄 (Maven 標準目錄結構) */
public static String getProjectRoot() {
String classPath = getClassOutputPath(PathUtils.class);
// target/classes -> 項目根
return new File(classPath).getParentFile().getParentFile().getAbsolutePath();
}
/** 獲取 resources 目錄絕對路徑 */
public static String getResourcesPath() {
return getProjectRoot() + File.separator + "src" + File.separator + "main" + File.separator + "resources";
}
/** 安全讀取類路徑資源 (兼容 JAR/爆炸目錄) */
public static InputStream getResourceAsStream(String classpath) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(classpath);
}
public static String readResourceAsString(String classpath) throws IOException {
try (InputStream is = getResourceAsStream(classpath)) {
if (is == null) throw new FileNotFoundException("資源不存在: " + classpath);
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
Web 環境 (Servlet/JSP/Controller/Filter/Listener)
1. ServletContext 途徑 (最權威、最常用)
// 在 Servlet/Filter/Listener 中
String webRoot = getServletContext().getRealPath("/");
// 如: /opt/tomcat/webapps/myapp/ 或 D:\apache-tomcat\webapps\myapp\
// 在 Controller 中 (注入)
@Autowired ServletContext servletContext;
String webRoot = servletContext.getRealPath("/");
// 在 JSP 中
<%= application.getRealPath("/") %>
${pageContext.servletContext.getRealPath("/")}
2. Request 相關路徑
// 請求 URL 路徑 (含 ContextPath)
String requestURI = request.getRequestURI(); // /myapp/api/users
String contextPath = request.getContextPath(); // /myapp
String servletPath = request.getServletPath(); // /api/users
String pathInfo = request.getPathInfo(); // null 或 /extra/path
// 獲取當前 JSP/頁面的物理路徑
String currentPageRealPath = request.getRealPath(request.getRequestURI());
// 注意: 此方法在某些容器/版本可能返回 null,推薦用 ServletContext
3. Spring ResourceLoader 統一抽象 (推薦)
@Autowired ResourceLoader resourceLoader;
// classpath: 類路徑
Resource res1 = resourceLoader.getResource("classpath:config/app.yml");
// file: 文件系統絕對路徑
Resource res2 = resourceLoader.getResource("file:/data/config/app.yml");
// http: 網絡資源
Resource res3 = resourceLoader.getResource("https://example.com/config.json");
// 相對路徑 (相當於 classpath:)
Resource res4 = resourceLoader.getResource("templates/email.ftl");
// 統一讀取
try (InputStream is = res.getInputStream()) { ... }
String absolutePath = res.getFile().getAbsolutePath(); // 注意: classpath: 在 JAR 中會拋異常
4. Spring Boot 專用: @Value 注入路徑
@Value("${file.upload-dir:/tmp/uploads}")
String uploadDir; // 支持 ${...} 佔位符、默認值
@Value("classpath:static/banner.txt")
Resource bannerResource; // 直接注入 Resource 對象
特殊場景處理
JAR 包內讀取資源 (Spring Boot Fat Jar)
// 錯誤: new File(classLoader.getResource("config.yml").getFile()) -> FileNotFoundException (jar:file:...!/)
// 正確: 流式讀取
try (InputStream is = MyClass.class.getResourceAsStream("/config.yml")) {
// 或 Spring Resource
Resource resource = new ClassPathResource("config.yml");
try (InputStream is = resource.getInputStream()) { ... }
}
// 需要文件路徑時 (如第三方庫只接受 File):複製到臨時文件
File tempFile = File.createTempFile("config", ".yml");
try (InputStream is = getResourceAsStream("config.yml")) {
Files.copy(is, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
// 使用 tempFile ... 最後 deleteOnExit()
多模塊項目獲取模塊資源
// 模塊 A 讀取模塊 B 的 resources
// 1. 確保 B 對 A 有依賴
// 2. 使用 B 模塊下的某個類作為錨點
Class> anchor = com.moduleb.Bootstrap.class;
InputStream is = anchor.getResourceAsStream("/module-b-config.yml");
// 或 Spring
@Autowired @Qualifier("moduleBResourceLoader") ResourceLoader loader;
Resource res = loader.getResource("classpath:module-b-config.yml");
動態加載外部配置文件
// 指定外部配置目錄啟動: java -Dapp.config.dir=/etc/myapp -jar app.jar
@Value("${app.config.dir:./config}") String configDir;
public Properties loadExternalConfig(String fileName) {
File file = new File(configDir, fileName);
if (!file.exists()) return new Properties();
try (InputStream is = new FileInputStream(file)) {
Properties props = new Properties();
props.load(is);
return props;
} catch (IOException e) {
throw new RuntimeException("加載外部配置失敗: " + file.getAbsolutePath(), e);
}
}
路徑拼接與規範化工具
public final class Paths2 {
private Paths2() {}
/** 安全拼接,自動處理分隔符、..、.、重複分隔符 */
public static String join(String... parts) {
return Arrays.stream(parts)
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.joining(File.separator));
}
/** 獲取標準化絕對路徑 (解析 ..、.、符號鏈接) */
public static String normalize(String path) {
try {
return Paths.get(path).toRealPath().toString();
} catch (IOException e) {
return Paths.get(path).normalize().toString();
}
}
/** 判斷是否為絕對路徑 (跨平台) */
public static boolean isAbsolute(String path) {
return Paths.get(path).isAbsolute();
}
/** 獲取相對路徑 (from -> to) */
public static String relativize(String from, String to) {
return Paths.get(from).relativize(Paths.get(to)).toString();
}
}
避坑速查表
| 錯誤代碼 | 問題 | 正確寫法 |
new File(request.getRealPath("/")) | 請求可能未初始化、或非 Web 線程 | 注入 ServletContext 用 servletContext.getRealPath("/") |
ClassLoader.getResource("").getPath() 直接 new File | JAR 包中路徑為 jar:file:...!/ 非文件系統路徑 | 用 getResourceAsStream() 或 Spring Resource |
硬編碼 File.separator 拼接 URL | Windows 反斜杠導致 URL 失效 | URL 用 /,文件系統用 File.separator 或 Paths.get() |
request.getContextPath() 為空串 | ROOT 應用 (部署為 ROOT.war 或 contextPath=/) | 拼接時判空: String base = ctx + (ctx.isEmpty() ? "" : ctx) |
相對路徑 ../config 在不同工作目錄下行為不一 | 依賴 user.dir (啟動目錄) | 改用絕對路徑、外部化配置 -Dconfig.dir=、或 Spring @Value |
總結
Java 路徑問題的核心在於**區分「文件系統路徑」與「類/資源路徑」**、**識別運行環境 (Web/非 Web、JAR/爆炸目錄)**、**統一使用 Spring Resource 抽象**。牢記:讀取資源用流 getResourceAsStream,寫入文件用絕對路徑 ServletContext.getRealPath 或外部化配置目錄,拼接用 Paths.get(),拒絕字符串拼接。