Java 標準庫提供多種途徑載入 .properties 設定檔,適用於不同部署場景(檔案系統、Classpath、JAR 內部、Servlet 容器、模組化應用)。本文系統整理 J2SE 六種核心 API 用法,附上完整可執行範例程式碼與單元測試,並補充 Servlet 環境下的第七種方式,幫助開發者根據場景選擇最適當的載入策略。
| 編號 | API 類別/方法 | 資料來源 | 適用場景 | 關鍵特性 |
|---|---|---|---|---|
| 1 | Properties.load(InputStream) | 檔案系統路徑 | 獨立應用、絕對/相對路徑 | 最通用、支援任意 InputStream |
| 2 | ResourceBundle.getBundle(baseName) | Classpath (資源束) | 國際化訊息、Classpath 資源 | 自動依 Locale 解析變體、快取機制 |
| 3 | new PropertyResourceBundle(InputStream) | 檔案系統/任意流 | 需 ResourceBundle 介面的舊代碼整合 | 橋接 Properties 與 ResourceBundle |
| 4 | Class.getResourceAsStream(path) | Classpath (相對類別位置) | 同包資源、模組內部資源 | 相對路徑解析、模組化友善 |
| 5 | ClassLoader.getResourceAsStream(path) | Classpath (根路徑) | 跨包資源、類別載入器隔離場景 | 絕對路徑 (從 classpath 根)、支援多載入器 |
| 6 | ClassLoader.getSystemResourceAsStream(path) | 系統類別載入器 Classpath | 啟動類別載入器可見資源、舊版相容 | 靜態方法、無需 Class 實例 |
package com.kindani;
import java.util.*;
import java.io.*;
public class JProperties {
public static final int BY_PROPERTIES = 1;
public static final int BY_RESOURCEBUNDLE = 2;
public static final int BY_PROPERTYRESOURCEBUNDLE = 3;
public static final int BY_CLASS = 4;
public static final int BY_CLASSLOADER = 5;
public static final int BY_SYSTEM_CLASSLOADER = 6;
public static Properties loadProperties(final String name, final int type) throws IOException {
Properties props = new Properties();
InputStream in = null;
try {
switch (type) {
case BY_PROPERTIES:
in = new BufferedInputStream(new FileInputStream(name));
props.load(in);
break;
case BY_RESOURCEBUNDLE:
ResourceBundle rb = ResourceBundle.getBundle(name, Locale.getDefault());
props = new ResourceBundleAdapter(rb);
break;
case BY_PROPERTYRESOURCEBUNDLE:
in = new BufferedInputStream(new FileInputStream(name));
rb = new PropertyResourceBundle(in);
props = new ResourceBundleAdapter(rb);
break;
case BY_CLASS:
in = JProperties.class.getResourceAsStream(name);
if (in == null) throw new FileNotFoundException("Classpath resource not found: " + name);
props.load(in);
break;
case BY_CLASSLOADER:
in = JProperties.class.getClassLoader().getResourceAsStream(name);
if (in == null) throw new FileNotFoundException("ClassLoader resource not found: " + name);
props.load(in);
break;
case BY_SYSTEM_CLASSLOADER:
in = ClassLoader.getSystemResourceAsStream(name);
if (in == null) throw new FileNotFoundException("System ClassLoader resource not found: " + name);
props.load(in);
break;
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
}
return props;
}
// Servlet 環境專用 (第 7 種方式)
public static Properties loadProperties(javax.servlet.ServletContext context, String path) throws IOException {
try (InputStream in = context.getResourceAsStream(path)) {
if (in == null) throw new FileNotFoundException("ServletContext resource not found: " + path);
Properties props = new Properties();
props.load(in);
return props;
}
}
// ResourceBundle 轉 Properties 適配器
public static class ResourceBundleAdapter extends Properties {
private final ResourceBundle bundle;
public ResourceBundleAdapter(ResourceBundle rb) {
this.bundle = rb;
Enumeration<String> keys = rb.getKeys();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
put(key, rb.getObject(key));
}
}
// 代理常用方法...
public ResourceBundle getBundle(String baseName) { return ResourceBundle.getBundle(baseName); }
public ResourceBundle getBundle(String baseName, Locale locale) { return ResourceBundle.getBundle(baseName, locale); }
public Enumeration<String> getKeys() { return bundle.getKeys(); }
public Locale getLocale() { return bundle.getLocale(); }
public Object getObject(String key) { return bundle.getObject(key); }
public String getString(String key) { return bundle.getString(key); }
public String[] getStringArray(String key) { return bundle.getStringArray(key); }
}
}
| 方式 | 路徑格式 | 範例 | 注意事項 |
|---|---|---|---|
| FileInputStream | 檔案系統絕對/相對路徑 | C:/config/app.propertiesconfig/app.properties | 相對路徑基於 JVM 工作目錄 (user.dir),部署易出錯 |
| Class.getResourceAsStream | 相對路徑 (不以 / 開頭) | com/kindani/test/LocalStrings.properties | 相對於該類別的包目錄;以 / 開頭則從 classpath 根 |
| ClassLoader.getResourceAsStream | 絕對路徑 (從 classpath 根,不以 / 開頭) | com/kindani/test/LocalStrings.properties | 統一從 classpath 根解析,跨包引用首選 |
| ResourceBundle.getBundle | 基礎名稱 (套件.檔名,無副檔名) | com.kindani.test.LocalStrings | 自動附加 _zh_TW 等 Locale 變體,屬性檔需放在 classpath |
package com.kindani.test;
import junit.framework.*;
import com.kindani.JProperties;
import java.util.Properties;
public class JPropertiesTest extends TestCase {
private static final String KEY = "helloworld.title";
private static final String VALUE = "Hello World!";
public void testLoadProperties() throws Exception {
Properties props;
// 1. 檔案系統路徑
props = JProperties.loadProperties("C:/IDEAP/Properties4Methods/src/com/kindani/test/LocalStrings.properties",
JProperties.BY_PROPERTIES);
assertEquals(VALUE, props.getProperty(KEY));
// 2. ResourceBundle (classpath)
props = JProperties.loadProperties("com.kindani.test.LocalStrings", JProperties.BY_RESOURCEBUNDLE);
assertEquals(VALUE, props.getProperty(KEY));
// 3. PropertyResourceBundle
props = JProperties.loadProperties("C:/IDEAP/Properties4Methods/src/com/kindani/test/LocalStrings.properties",
JProperties.BY_PROPERTYRESOURCEBUNDLE);
assertEquals(VALUE, props.getProperty(KEY));
// 4. 系統類別載入器
props = JProperties.loadProperties("com/kindani/test/LocalStrings.properties", JProperties.BY_SYSTEM_CLASSLOADER);
assertEquals(VALUE, props.getProperty(KEY));
// 5. 類別載入器
props = JProperties.loadProperties("com/kindani/test/LocalStrings.properties", JProperties.BY_CLASSLOADER);
assertEquals(VALUE, props.getProperty(KEY));
// 6. Class 相對路徑
props = JProperties.loadProperties("testLocalStrings.properties", JProperties.BY_CLASS);
assertEquals(VALUE, props.getProperty(KEY));
}
}
# $Id: LocalStrings.properties,v 1.1 2000/08/17 00:57:52 horwat Exp $
# Default localized resources for example servlets
# This locale is en_US
helloworld.title=Hello World!
requestinfo.title=Request Information Example
requestinfo.label.method=Method:
requestinfo.label.requesturi=Request URI:
requestinfo.label.protocol=Protocol:
requestinfo.label.pathinfo=Path Info:
requestinfo.label.remoteaddr=Remote Address:
requestheader.title=Request Header Example
requestparams.title=Request Parameters Example
requestparams.params-in-req=Parameters in this request:
requestparams.no-params=No Parameters, Please enter some
requestparams.firstname=First Name:
requestparams.lastname=Last Name:
cookies.title=Cookies Example
cookies.cookies=Your browser is sending the following cookies:
cookies.no-cookies=Your browser isn't sending any cookies
cookies.make-cookie=Create a cookie to send to your browser
cookies.name=Name:
cookies.value=Value:
cookies.set=You just sent the following cookie to your browser:
sessions.title=Sessions Example
sessions.id=Session ID:
sessions.created=Created:
sessions.lastaccessed=Last Accessed:
sessions.data=The following data is in your session:
sessions.adddata=Add data to your session
sessions.dataname=Name of Session Attribute:
sessions.datavalue=Value of Session Attribute:
Properties.load(FileInputStream) 或 Files.readString(Path) (Java 11+)ResourceBundle.getBundle() (自動 Locale 解析、快取)ClassLoader.getResourceAsStream() + Properties.load() (最通用、可控)Module.getResourceAsStream() 或 Class.getResourceAsStream() (模組邊界隔離)ServletContext.getResourceAsStream() (支援 WAR 內部、外部化配置)@PropertySource、Environment、ConfigurablePropertyResolver 抽象層ResourceBundle 內建快取,重複 getBundle 極快,但修改檔案需重啟或手動清除快取 (ResourceBundle.clearCache() Java 8+)Properties 無快取,每次載入皆讀取流,適合動態重載場景掌握六種標準 API 的適用邊界與路徑解析規則,是 Java 開發者處理配置管理的基本功。實務中建議封裝為統一的 ConfigurationService,屏蔽底層載入細節,提供型別安全的 getInt()、getDuration() 等存取方法,並整合框架配置抽象層,實現從開發到生產的無縫配置管理。