Java 「一次編寫,到處運行」的承諾建立在屏蔽平台差異之上。但應用往往需要知道:當前是 Windows 還是 Linux?用戶主目錄在哪?JDK 安裝路徑?工作目錄?java.util.Properties 與 System.getProperties() 正是通往這些運行時環境信息的標準接口。本文系統梳理系統屬性獲取、自定義屬性文件操作、安全策略配置,並給出生產級最佳實踐。
| 屬性鍵 | 含義 | Windows 示例 | Linux 示例 |
|---|---|---|---|
file.separator | 文件分隔符 | \ | / |
path.separator | 路徑分隔符 | ; | : |
user.home | 用戶主目錄 | C:\Users\name | /home/name |
user.dir | 當前工作目錄 | D:\project | /opt/app |
java.home | JRE 安裝目錄 | C:\Java\jre1.8 | /usr/lib/jvm/java-8 |
java.version | Java 版本 | 1.8.0_202 | 11.0.12 |
os.name | 操作系統名稱 | Windows 10 | Linux |
os.arch | CPU 架構 | amd64 | aarch64 |
line.separator | 行分隔符 | \r\n | \n |
獲取所有屬性:System.getProperties().list(System.out);
Properties props = System.getProperties(); // 系統屬性
// 或
Properties props = new Properties();
props.load(new FileInputStream("config.properties")); // 文件屬性
// 獲取值
String value = props.getProperty("key"); // 無則返回 null
String value = props.getProperty("key", "default"); // 無則返回默認值
// 檢查存在性
boolean hasKey = props.containsKey("key");
boolean hasValue = props.contains("value"); // 注意:查找值而非鍵
// 遍歷
props.forEach((k, v) -> System.out.println(k + "=" + v));
props.stringPropertyNames().forEach(k -> System.out.println(k + "=" + props.getProperty(k)));
// 輸出調試
props.list(System.out); // 打印到 PrintStream
props.list(new PrintWriter(System.out)); // 打印到 PrintWriter
props.setProperty("key", "value"); // 僅 String,推薦
props.put("key", "value"); // 繼承自 Hashtable,接受 Object
// 移除
props.remove("key"); // 返回舊值
// 保存到文件(需輸出流)
props.store(new FileOutputStream("config.properties"), "配置文件註釋");
// XML 格式保存(JDK 1.5+)
props.storeToXML(new FileOutputStream("config.xml"), "XML 註釋", "UTF-8");
public class ConfigManager {
private static final Properties PROPS = new Properties();
private static final String CONFIG_FILE = "app.properties";
static {
load();
}
private static void load() {
try (InputStream in = ConfigManager.class.getClassLoader()
.getResourceAsStream(CONFIG_FILE)) {
if (in != null) {
PROPS.load(in);
} else {
// 回退到文件系統
try (FileInputStream fin = new FileInputStream(CONFIG_FILE)) {
PROPS.load(fin);
} catch (FileNotFoundException ignored) {
// 使用默認值
}
}
} catch (IOException e) {
throw new ExceptionInInitializerError("配置加載失敗: " + e.getMessage());
}
}
public static String get(String key) { return PROPS.getProperty(key); }
public static String get(String key, String def) { return PROPS.getProperty(key, def); }
public static int getInt(String key, int def) {
return Integer.parseInt(PROPS.getProperty(key, String.valueOf(def)));
}
// 運行時動態修改並持久化
public static void setAndSave(String key, String value) {
PROPS.setProperty(key, value);
try (FileOutputStream out = new FileOutputStream(CONFIG_FILE)) {
PROPS.store(out, "Updated at " + Instant.now());
} catch (IOException e) {
log.error("配置保存失敗", e);
}
}
}
# app.properties
# 數據庫配置
db.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
db.username=app_user
db.password=encrypted:xxx
db.pool.size=20
# 功能開關
feature.new-ui.enabled=true
feature.cache.ttl.seconds=3600
# 路徑配置(使用系統屬性佔位符,需自行解析)
upload.dir=${user.home}/uploads
log.dir=${user.dir}/logs
Java 安全架構基於 Policy 對象,通過策略文件授予代碼特定權限。三級策略加載順序:
${java.home}/conf/security/java.policy(JDK 9+)或 lib/security/java.policy${user.home}/.java.policy-Djava.security.policy=app.policy
/* app.policy */
grant codeBase "file:/opt/myapp/-" {
// 文件讀寫權限
permission java.io.FilePermission "/opt/myapp/data/-", "read,write";
permission java.io.FilePermission "/tmp/-", "read,write,delete";
// 網絡連接權限
permission java.net.SocketPermission "db.internal:3306", "connect,resolve";
permission java.net.SocketPermission "api.external.com:443", "connect,resolve";
// 系統屬性讀取權限
permission java.util.PropertyPermission "app.config.*", "read";
// 反射訪問權限
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
};
// 特定簽名代碼授予更高權限
grant signedBy "mycompany" {
permission java.security.AllPermission;
};
// 命令行啟動
java -Djava.security.manager -Djava.security.policy=app.policy -jar app.jar
// 代碼中啟用(main 方法最早)
System.setSecurityManager(new SecurityManager());
注意:JDK 17 起 SecurityManager 被棄用,建議遷移到容器級隔離、模塊系統強封裝、OS 級沙箱。
// 優先級:默認值 < application.properties < profile.properties < 環境變量 < 命令行參數
Properties loadLayeredConfig() {
Properties props = new Properties();
// 1. 默認配置(打包在 jar 內)
props.load(getClass().getResourceAsStream("/default.properties"));
// 2. 應用配置(部署目錄)
loadIfExists(props, "config/application.properties");
// 3. 環境配置(如 prod.properties)
String profile = System.getProperty("spring.profiles.active", "dev");
loadIfExists(props, "config/" + profile + ".properties");
// 4. 環境變量覆蓋(自動映射 DB_URL → db.url)
System.getenv().forEach((k, v) -> {
String propKey = k.toLowerCase().replace('_', '.');
props.setProperty(propKey, v);
});
// 5. 命令行參數 -Dxxx=yyy
System.getProperties().forEach((k, v) -> props.setProperty((String)k, (String)v));
return props;
}
// 加密工具類(簡化示例,生產用 Jasypt/Vault)
public class CryptoConfig {
private static final String PREFIX = "ENC(";
public static String decryptIfNeeded(String value) {
if (value != null && value.startsWith(PREFIX) && value.endsWith(")")) {
String encrypted = value.substring(PREFIX.length(), value.length() - 1);
return AESUtil.decrypt(encrypted, getMasterKey());
}
return value;
}
// 包裝 Properties.getProperty
public static String getProperty(Properties props, String key) {
return decryptIfNeeded(props.getProperty(key));
}
}
| 場景 | 推薦方案 | 關鍵點 |
|---|---|---|
| 獲取運行環境信息 | System.getProperty(key) | 跨平台路徑用 File.separator、File.pathSeparator |
| 應用業務配置 | Properties 文件 + 分層覆蓋 | 類加載器優先、環境變量覆蓋、敏感信息加密 |
| 用戶偏好設置 | Preferences API | 自動跨平台持久化、無需管理文件路徑 |
| 安全權限控制 | Policy 文件 + SecurityManager(舊版本) | 最小權限原則、基於 codeBase/signedBy 授權 |
| 現代雲原生配置 | Spring Cloud Config / Consul / etcd / Kubernetes ConfigMap | 動態刷新、版本控制、集中管理 |
掌握 Properties 與系統屬性,是 Java 開發者處理「環境感知」與「外部化配置」的基本功。從單機屬性文件到分布式配置中心,核心原理始終是:**配置與代碼分離、環境差異外部化、敏感信息加密、變更可追蹤**。