Properties文件是Java生态里最经典的配置载体,从JDK 1.0时代就已经存在。它以简单的键值对形式存储配置,几乎每个Java项目都离不开它。不过很多开发者虽然天天和Properties打交道,却对它的编码规则、加载机制以及各类库的替代方案了解得不深,遇到中文乱码或者需要动态刷新配置时往往束手无策。本文从Properties类的底层行为讲起,逐步扩展到主流配置类库的选型与实战。

Properties类的核心用法与常见陷阱
java.util.Properties继承自Hashtable,本质上是一个线程安全的键值对容器,专门为字符串类型的配置设计。加载文件时最常用的方法是load(InputStream),示例如下:
Properties props = new Properties();
// 从类路径加载配置文件
try (InputStream in = Main.class.getClassLoader()
.getResourceAsStream("config.properties")) {
if (in == null) {
throw new IllegalStateException("配置文件不存在");
}
props.load(in);
} catch (IOException e) {
throw new UncheckedIOException("读取配置失败", e);
}
String dbUrl = props.getProperty("db.url", "jdbc:mysql://localhost:3306/test");
System.out.println("数据库地址: " + dbUrl);
// 写回配置文件
try (Writer w = new FileWriter("config-output.properties")) {
props.store(w, "自动生成的配置");
}这里有几个细节值得注意。第一,getProperty支持默认值,比先get再判空优雅得多。第二,从JDK 9开始推荐使用Reader或Writer作为输入输出源,这样可以显式指定字符编码,避免平台默认编码带来的兼容问题。第三,注释行以#或!开头,键值分隔符支持=、:以及空白符,解析规则比想象中宽松。
最常见的坑是中文乱码。Properties文件的历史规范规定其流式读写采用ISO 8859-1编码,而大部分编辑器保存文件用的是UTF-8或GBK,两者不一致就会出现乱码。解决办法有三种:一是从JDK 9起直接使用props.load(new InputStreamReader(in, StandardCharsets.UTF_8)),让Reader版本的API按UTF-8解析;二是老项目里把中文转成Unicode转义形式,比如用native2ascii工具把“用户名”写成\u7528\u6237\u540d;三是改用XML格式存储配置,Properties原生支持loadFromXML和storeToXML方法:
Properties props = new Properties();
props.setProperty("app.name", "订单服务");
props.setProperty("app.timeout", "30");
// 以XML格式保存,默认编码UTF-8,天然支持中文
try (OutputStream os = new FileOutputStream("config.xml")) {
props.storeToXML(os, "系统配置", "UTF-8");
}
// 重新加载
Properties loaded = new Properties();
try (InputStream is = new FileInputStream("config.xml")) {
loaded.loadFromXML(is);
}
System.out.println(loaded.getProperty("app.name"));XML格式的配置文件自带编码声明,读写时不会有编码歧义,缺点是文件体积更大、手工编辑不如键值对方便。团队可以根据实际情况在两种格式之间取舍。
封装一个健壮的Properties工具类
直接在业务代码里散落Properties操作会导致资源管理混乱、异常处理重复。实际项目中建议封装一个工具类,统一处理加载、类型转换和默认值。下面是一个比较完整的例子:
public final class PropsUtil {
private final Properties props = new Properties();
public PropsUtil(String classpathFile) {
try (InputStream in = PropsUtil.class.getClassLoader()
.getResourceAsStream(classpathFile)) {
if (in == null) {
throw new IllegalArgumentException("找不到文件: " + classpathFile);
}
// 显式使用UTF-8,避免乱码
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
} catch (IOException e) {
throw new ExceptionInInitializerError(e);
}
}
public String get(String key) {
return props.getProperty(key);
}
public String get(String key, String defaultValue) {
return props.getProperty(key, defaultValue);
}
public int getInt(String key, int defaultValue) {
String v = props.getProperty(key);
return v == null ? defaultValue : Integer.parseInt(v.trim());
}
public boolean getBoolean(String key, boolean defaultValue) {
String v = props.getProperty(key);
return v == null ? defaultValue : Boolean.parseBoolean(v.trim());
}
public long getLong(String key, long defaultValue) {
String v = props.getProperty(key);
return v == null ? defaultValue : Long.parseLong(v.trim());
}
}这个工具类有几个设计考量。构造函数中把InputStream立即消费完毕并关闭,后续读取不再持有文件句柄,避免了资源泄漏。类型转换方法统一做trim处理,因为配置文件里值末尾的空格经常引发诡异问题。抛出ExceptionInInitializerError意味着配置加载失败时快速失败,让问题在启动阶段暴露而不是运行中途才炸。
如果需要支持配置文件修改后自动生效,可以在工具类基础上增加基于时间戳的检测逻辑:启动一个守护线程,每隔几秒检查文件的lastModified,发现变化就重新加载到新的Properties对象,再用原子引用替换旧对象。这种方案实现简单,但要注意读取时要通过快照访问,防止遍历过程中集合被并发修改。
主流配置类库对比与选型建议
JDK自带的Properties功能毕竟基础,社区出现了不少更强大的配置类库,下面横向对比三个最常用的方案。
Apache Commons Configuration是最老牌的选择,它的优势在于格式兼容广,同时支持properties、XML、JSON、YAML、INI等格式,还提供了配置合并、分层覆盖、自动重载等高级特性。开启自动重载只需要几行配置:
Parameters params = new Parameters();
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
.configure(params.properties()
.setFileName("app.properties")
.setListDelimiterHandler(new DefaultListDelimiterHandler(','))
.setReloadingDetectorFactory(
new DefaultReloadingDetectorFactory()));
// 每5秒检测一次文件变化
ReloadingFileBasedConfigurationBuilder<?> rBuilder =
(ReloadingFileBasedConfigurationBuilder<?>) builder;
rBuilder.getReloadingController()
.replaceReloadChecks(
() -> {}, 5000);
Configuration config = builder.getConfiguration();
System.out.println(config.getString("app.name"));
// 一个key对应多个值时自动转数组,例如 servers=192.168.1.1,192.168.1.2
String[] servers = config.getStringArray("servers");Typesafe Config(现称lightbend Config)是Scala和Java生态中广泛使用的方案,Play框架和Akka都基于它。它最突出的特性是支持HOCON格式,这种格式允许嵌套结构和变量替换,表达能力远超扁平的键值对:
// application.conf 内容:
// db {
// host = "127.0.0.1"
// port = 3306
// url = "jdbc:mysql://"${db.host}":"${db.port}"/order"
// }
Config config = ConfigFactory.load();
String url = config.getString("db.url");
int port = config.getInt("db.port");
// 支持默认值链:先查系统属性,再查配置文件
Config effective = ConfigFactory.systemProperties()
.withFallback(ConfigFactory.load());Spring的PropertySource体系则把配置管理融入了依赖注入。Spring会自动加载application.properties,并通过@Value和@ConfigurationProperties把配置绑定到Bean上,多个PropertySource之间形成优先级链,环境变量可以覆盖文件配置。如果项目已经基于Spring,几乎没必要再额外引入其他配置类库。
| 方案 | 支持格式 | 嵌套结构 | 自动重载 | 适用场景 |
|---|---|---|---|---|
| JDK Properties | properties、XML | 不支持 | 需自己实现 | 小型工具、脚本 |
| Commons Configuration | properties、XML、YAML、INI等 | 有限支持 | 原生支持 | 需要热更新的桌面或服务端程序 |
| Typesafe Config | HOCON、JSON、properties | 完整支持 | 不支持 | 结构化配置、多环境合并 |
| Spring PropertySource | properties、YAML等 | YAML支持 | 配合Cloud Config | Spring系项目 |
选型上可以遵循这样的思路:纯工具类项目或遗留系统维护,JDK Properties加一个自封装工具类足够;需要配置热更新和多格式混合的桌面应用,选Commons Configuration;配置结构复杂、需要继承与覆盖的项目,Typesafe Config的表达力最强;而Spring Boot项目直接使用框架自带机制即可。无论选哪种方案,都要在项目初期就统一编码规范和配置文件组织方式,否则后期配置散落各处、命名混乱的代价会远高于引入任何类库的成本。
Java配置文件Properties类库修改时间:2026-09-08 12:43:10