java.util.Properties 是 Java 處理鍵值配置的基石。本文梳理 JDK 標準 API 的 6 種加載方式,對比適用場景,並補充 Web 容器場景下的最佳實踐。
Properties props = new Properties();
props.load(inputStream); // 核心加載方法
String value = props.getProperty("key", "defaultValue"); // 安全取值
| # | 方式 | 代碼示例 | 路徑解析規則 | 適用場景 |
|---|---|---|---|---|
| 1 | 文件系統絕對/相對路徑 | InputStream in = new BufferedInputStream(
new FileInputStream("config/app.properties")); | 相對於 user.dir(啟動工作目錄) | 獨立應用、外部化配置、Docker 掛載配置文件 |
| 2 | ResourceBundle 機制 | ResourceBundle rb = ResourceBundle.getBundle("config/app", Locale.getDefault()); | 類路徑下 config/app.properties(無後綴),支持 i18n 變體 | 國際化配置、模塊化項目、偏好「約定優於配置」 |
| 3 | PropertyResourceBundle 直接構造 | InputStream in = new BufferedInputStream(
new FileInputStream("config/app.properties"));
ResourceBundle rb = new PropertyResourceBundle(in); | 同方式 1,但返回 ResourceBundle 只讀視圖 | 需只讀、國際化特性時 |
| 4 | Class.getResourceAsStream() | InputStream in = MyClass.class.getResourceAsStream("/config/app.properties"); | 以 / 開頭:類路徑根;不以 / 開頭:當前類包目錄 | 類內部加載同包/項目內配置,最常用 |
| 5 | ClassLoader.getResourceAsStream() | InputStream in = MyClass.class.getClassLoader()
.getResourceAsStream("config/app.properties"); | 永遠從類路徑根查找,不可帶 / | 工具類、框架代碼、跨模塊加載共享配置 |
| 6 | ClassLoader.getSystemResourceAsStream() | InputStream in = ClassLoader.getSystemResourceAsStream("config/app.properties"); | 系統類加載器路徑(-cp 指定路徑) | 啟動參數指定配置、Agent、命令行工具 |
Class vs ClassLoader 路徑規則// 假設類結構:com.demo.util.ConfigUtil
// 資源位置:src/main/resources/config/app.properties (編譯到 classes 根目錄)
ConfigUtil.class.getResourceAsStream("/config/app.properties") // ✅ 根路徑
ConfigUtil.class.getResourceAsStream("config/app.properties") // ❌ 找 com/demo/util/config/app.properties
ConfigUtil.class.getClassLoader().getResourceAsStream("config/app.properties") // ✅ 根路徑
ConfigUtil.class.getClassLoader().getResourceAsStream("/config/app.properties") // ❌ ClassLoader 不認 /
口訣:Class 帶 / 找根,不帶找包;ClassLoader 只認相對根路徑,禁帶 /。
@WebListener
public class ConfigLoader implements ServletContextListener {
private static final String CONFIG_PATH = "/WEB-INF/classes/config/app.properties";
public static Properties PROPS = new Properties();
@Override
public void contextInitialized(ServletContextEvent sce) {
try (InputStream in = sce.getServletContext().getResourceAsStream(CONFIG_PATH)) {
if (in == null) throw new IllegalStateException("Config not found: " + CONFIG_PATH);
PROPS.load(in);
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
}
// 業務代碼直接用
String url = ConfigLoader.PROPS.getProperty("db.url");
優勢:
① 容器管理生命周期,啟動失敗快速暴露
② 支持 WAR 內部路徑、解壓目錄、JAR 內資源統一訪問
③ 避免類加載器層級差異導致的找不到資源
ConfigService,內部按優先級嘗試:系統屬性 -Dconfig.file → 環境變量 → ServletContext → 類路徑默認值。WatchService 監聽文件變更 + ReentrantReadWriteLock 保護 Properties 實例,或接入 Apollo/Nacos/Spring Cloud Config。getInt()、getBoolean()、getDuration() 封裝轉換與默認值。| 場景 | 推薦方式 | 關鍵點 |
|---|---|---|
| Spring/Spring Boot 項目 | @Value / @ConfigurationProperties / Environment | 框架已封裝,勿手工 load |
| 獨立 Java 應用 / 命令行工具 | 方式 5 (ClassLoader) + 外部文件優先 | -Dconfig.file=/opt/app/config.properties |
| Web 應用 (非 Spring) | ServletContextListener + getResourceAsStream | 生命周期綁定,路徑穩定 |
| 庫/框架代碼 / 多模塊共享配置 | 方式 5 (ClassLoader) | 不依賴具體類,跨模塊通用 |
| 國際化配置 | 方式 2 (ResourceBundle) | 自動解析 _zh_CN 變體 |
掌握這 6 種標準姿勢與路徑規則,配置加載不再是「玄學」,而是可控、可測、可遷移的基礎設施能力。