| 類型 | 示例 | 適用 API | 核心特徵 |
|---|---|---|---|
| URI 絕對路徑 | file:/D:/project/bin/com/Test.class | java.net.URL/URI, Class.getResource() | 標準化、可轉換、跨平台 |
| 本地絕對路徑 | D:/project/bin/com/Test.class | java.io.File, FileInputStream | 作業系統原生、依賴平台分隔符 |
| ClassPath 相對路徑 | com/Test.class (相對 bin/) | ClassLoader.getResource(), ResourceBundle | 獨立於部署位置、JAR 內部可訪問 |
| 用戶目錄相對路徑 | config/app.properties (相對 user.dir) | new File("relative"), FileReader | **極度危險**,J2EE 中不可預測 |
默認情況下,java.io 包類根據
user.dir解析相對路徑。此目錄由 JVM 啟動目錄決定,J2EE 容器中可能指向服務器安裝目錄、部署目錄、臨時目錄等,完全不可控。
new File("config.properties") — 生產環境必死new FileInputStream("../conf/app.xml") — 遷移即失效// 獲取 ClassPath 根目錄 URI
Thread.currentThread().getContextClassLoader().getResource("")
// 推薦:線程上下文類加載器,支援 Web 應用類加載器隔離
// 讀取資源
ClassLoader cl = Thread.currentThread().getContextClassLoader();
InputStream is = cl.getResourceAsStream("config/app.properties");
URL url = cl.getResource("config/app.properties");
ResourceBundle bundle = ResourceBundle.getBundle("config.app"); // 配置文件 config/app.properties
String webRoot = servletContext.getRealPath("/"); // 如 /opt/tomcat/webapps/myapp/
WEB-INF 外部資源路徑:webRoot + "WEB-INF/classes/config.properties"null// Spring 模式
classpath:config/app.properties // ClassPath 內
file:${web.root}/WEB-INF/config/app.properties // Web 根相對
classpath*:config/app.properties // 多 ClassPath 合併
解決「ClassPath 外資源訪問」與「Web/非 Web 環境統一」雙重難題。
../ 向上遍歷 ClassPath 根目錄,訪問項目根、src/、WEB-INF/ 等任意位置java.net.URL,可轉 InputStream/File/URIpublic static URL getExtendResource(String relativePath) throws MalformedURLException {
// 1. 無 ../ 直接委託 ClassLoader
if (!relativePath.contains("../")) return getClassLoader().getResource(relativePath);
// 2. 獲取 ClassPath 根絕對 URI
String classPathRoot = getClassLoader().getResource("").toString(); // file:/D:/project/bin/
// 3. 解析 ../ 數量與剩餘路徑
int lastDotDot = relativePath.lastIndexOf("../");
String upPath = relativePath.substring(0, lastDotDot + 3); // ../../../
String remainPath = relativePath.substring(lastDotDot + 3); // config/app.properties
int levels = countOccurrences(upPath, "../"); // 計算向上層數
String targetRoot = cutLastSegments(classPathRoot, "/", levels); // 向上截取
// 4. 組裝目標絕對 URI
String absoluteURI = targetRoot + remainPath;
return new URL(absoluteURI);
}
private static int countOccurrences(String src, String target) {
int count = 0, idx = 0;
while ((idx = src.indexOf(target, idx)) != -1) { count++; idx += target.length(); }
return count;
}
private static String cutLastSegments(String uri, String sep, int n) {
for (int i = 0; i < n; i++) {
int pos = uri.lastIndexOf(sep, uri.length() - 2); // 排除末尾 /
uri = uri.substring(0, pos + 1);
}
return uri;
}
// Web 環境:ClassPath = /WEB-INF/classes/
URL url1 = ClassLoaderUtil.getExtendResource("log4j.properties"); // ClassPath 內
URL url2 = ClassLoaderUtil.getExtendResource("../config/app.properties"); // WEB-INF/config/
URL url3 = ClassLoaderUtil.getExtendResource("../../src/main/resources/db.xml"); // 項目源碼目錄
// JUnit/Main:ClassPath = target/classes/
// 相同相對路徑自動定位到對應位置,零修改遷移
public class ClassLoaderUtil {
private static final Log log = LogFactory.getLog(ClassLoaderUtil.class);
public static ClassLoader getClassLoader() {
return ClassLoaderUtil.class.getClassLoader();
}
public static InputStream getStream(String relativePath) throws IOException {
if (!relativePath.contains("../"))
return getClassLoader().getResourceAsStream(relativePath);
return getStream(getExtendResource(relativePath));
}
public static Properties getProperties(String resource) {
Properties props = new Properties();
try { props.load(getStream(resource)); }
catch (IOException e) { throw new RuntimeException("Load props failed: " + resource, e); }
return props;
}
// getExtendResource, countOccurrences, cutLastSegments 見上文
// main 方法測試入口
}
user.dir 相對路徑 — 定時炸彈ClassLoaderUtil 實現全域訪問ClassLoader.getResource("") 動態獲取基準路徑ResourceLoader.getResource("classpath:...", "file:...", "servletContext:...")ConfigProvider.getConfig().getValue("path", String.class) 多源統一Resource 接口理解底層路徑解析機制,是駕馭上層框架、排查資源加載失效的根本功力。