版权声明
- 本文原创作者:谷哥的小弟
- 作者博客地址:http://blog.csdn.net/lfdfhl

一、引言
在 Java 应用开发中,配置属性(Properties)的加载与解析 是基础且关键的功能。Spring Framework 提供了 org.springframework.core.io.support.PropertiesLoaderUtils 工具类,用于从各种资源(如文件系统、类路径、URL 等)高效、安全地加载 java.util.Properties 对象。
该工具类建立在 Spring 的 统一资源抽象(Resource 接口) 之上,屏蔽了底层资源位置的差异,同时处理了字符编码、输入流管理、异常转换等细节,为上层框架(如 PropertyPlaceholderConfigurer、@PropertySource、Spring Boot 的 Environment)提供了可靠的属性加载能力。
本文将对 PropertiesLoaderUtils 进行全面、深入、严谨的技术剖析。我们将从其设计目标、核心方法、内部实现机制、字符编码处理、与 Resource 抽象的集成、典型使用场景、源码关键逻辑,到最佳实践逐一展开,并辅以关键代码解读,力求揭示 Spring 如何以简洁而健壮的方式实现配置属性的加载。
二、设计目标与核心职责
2.1 设计目标
- 统一属性加载入口 :支持从任意
Resource加载 Properties; - 自动资源管理:确保输入流正确关闭,避免资源泄漏;
- 编码透明化:默认使用 ISO-8859-1(兼容 Java Properties 规范),但支持自定义编码;
- 异常安全 :将底层
IOException转换为 Spring 风格的 unchecked 异常; - 支持多资源合并:可将多个 Properties 文件合并为一个逻辑配置集。
2.2 核心职责
| 职责 | 说明 |
|---|---|
| 单资源加载 | 从一个 Resource 加载 Properties |
| 多资源合并加载 | 从多个 Resource 按顺序加载并合并(后加载的覆盖先加载的) |
| 编码控制 | 支持指定字符编码(如 UTF-8) |
| 流生命周期管理 | 自动打开和关闭 InputStream |
三、类结构与定位
java
package org.springframework.core.io.support;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
- 工具类:全静态方法,无状态,线程安全;
- 依赖 :仅依赖
Resource和标准 Java I/O; - 所属模块 :
spring-core,是 Spring 基础设施的一部分。
✅ 设计哲学 :
"关注点分离" ------ 将属性加载逻辑与资源定位、流管理解耦。
四、核心方法与源码剖析
4.1 loadProperties(Resource resource)
这是最常用的方法,用于从单个资源加载 Properties。
源码实现:
java
public static Properties loadProperties(Resource resource) throws IOException {
Properties props = new Properties();
fillProperties(props, resource);
return props;
}
private static void fillProperties(Properties props, Resource resource) throws IOException {
try (InputStream is = resource.getInputStream()) {
props.load(is);
}
}
关键点分析:
- try-with-resources :
- 自动调用
is.close(),确保流关闭; - 即使
props.load()抛出异常,流仍会被释放;
- 自动调用
- 委托
Properties.load(InputStream):- 使用 Java 标准库方法,兼容
.properties格式; - 默认编码为 ISO-8859-1(Java Properties 规范要求);
- 使用 Java 标准库方法,兼容
- 异常传播 :
- 抛出
IOException,由调用者处理或包装。
- 抛出
📌 注意 :此方法不支持 UTF-8 编码的属性文件 (除非使用
\uXXXX转义)。若需 UTF-8,应使用fillProperties(props, resource, encoding)。
4.2 fillProperties(Properties props, Resource resource, String encoding)
支持指定字符编码的重载方法。
源码实现:
java
public static void fillProperties(Properties props, Resource resource, String encoding) throws IOException {
try (InputStream is = resource.getInputStream()) {
// 使用 InputStreamReader 包装以指定编码
try (InputStreamReader isr = new InputStreamReader(is, encoding)) {
props.load(isr);
}
}
}
关键点分析:
- 双层 try-with-resources :
- 先关闭
InputStreamReader,再关闭InputStream; - 符合资源关闭的依赖顺序;
- 先关闭
- 编码灵活性 :
- 支持 UTF-8、GBK 等任意编码;
- 适用于国际化或非 ASCII 键值场景;
- 兼容性 :
- 当
encoding = "ISO-8859-1"时,行为与无编码版本一致。
- 当
✅ 最佳实践 :
若属性文件包含中文等非 Latin 字符,应显式指定
UTF-8编码。
4.3 loadAllProperties(String location, @Nullable ClassLoader classLoader)
从类路径加载单个 Properties 文件(简化 API)。
源码实现:
java
public static Properties loadAllProperties(String location, @Nullable ClassLoader classLoader) throws IOException {
// 构造 ClassPathResource
Resource resource = new ClassPathResource(location, classLoader);
return loadProperties(resource);
}
- 便捷方法 :隐藏
Resource创建细节; - 适用场景 :快速加载类路径下的配置(如
application.properties)。
4.4 loadProperties(Resource... resources)
按顺序加载并合并多个资源,后加载的属性覆盖先加载的。
源码实现:
java
public static Properties loadProperties(Resource... resources) throws IOException {
Properties props = new Properties();
for (Resource resource : resources) {
fillProperties(props, resource);
}
return props;
}
合并语义示例:
properties
# base.properties
app.name=MyApp
app.version=1.0
# override.properties
app.version=2.0
app.env=prod
java
Properties merged = PropertiesLoaderUtils.loadProperties(
new ClassPathResource("base.properties"),
new ClassPathResource("override.properties")
);
// 结果:
// app.name=MyApp
// app.version=2.0 ← 被覆盖
// app.env=prod
✅ 价值:实现"默认配置 + 环境覆盖"的典型模式。
五、与 Spring 配置体系的集成
5.1 在 PropertySourcesPlaceholderConfigurer 中的应用
java
// 旧版 XML 配置
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:default.properties</value>
<value>classpath:${env}.properties</value>
</list>
</property>
</bean>
内部实现:
java
// PropertyPlaceholderConfigurer.java
protected void loadProperties(Properties props) throws IOException {
for (Resource location : this.locations) {
PropertiesLoaderUtils.fillProperties(props, location, this.fileEncoding);
}
}
5.2 在 @PropertySource 中的间接使用
虽然 @PropertySource 由 ConfigurationClassParser 处理,但最终通过 ResourcePropertySource 加载,而后者内部使用:
java
// ResourcePropertySource.java
public ResourcePropertySource(String name, Resource resource, String encoding) throws IOException {
super(name, PropertiesLoaderUtils.loadProperties(resource, encoding));
}
✅ 结论 :
PropertiesLoaderUtils是 Spring 属性加载体系的底层基石。
六、字符编码处理详解
6.1 Java Properties 默认编码
- 规范要求 :
.properties文件必须使用 ISO-8859-1 编码; - 非 Latin 字符 :必须使用 Unicode 转义(如
\u4E2D\u6587); - 历史原因:Java 1.0 设计时未考虑多语言。
6.2 UTF-8 支持方案
现代开发中,开发者常直接使用 UTF-8 编写属性文件。此时必须:
-
显式指定编码 :
javaProperties props = new Properties(); PropertiesLoaderUtils.fillProperties(props, resource, "UTF-8"); -
或使用
.xml格式 (Properties.loadFromXML()原生支持 UTF-8)。
⚠️ 陷阱 :
若未指定编码且文件为 UTF-8,中文将显示为乱码(因被当作 ISO-8859-1 解析)。
七、异常处理与资源安全
7.1 异常类型
- 抛出
IOException:包括FileNotFoundException、MalformedInputException等; - 不包装为 Spring 异常:保持 I/O 异常语义清晰,便于调用者处理。
7.2 资源泄漏防护
所有方法均使用 try-with-resources,确保:
- 即使
props.load()失败,流也会关闭; - 不依赖 GC 回收(
finalize()不可靠)。
✅ 可靠性保障:符合 Java 资源管理最佳实践。
八、典型使用场景
8.1 手动加载配置
java
try {
Properties dbProps = PropertiesLoaderUtils.loadAllProperties("database.properties");
String url = dbProps.getProperty("db.url");
} catch (IOException e) {
throw new IllegalStateException("Failed to load database properties", e);
}
8.2 多环境配置合并
java
Resource[] resources = {
new ClassPathResource("application-default.properties"),
new ClassPathResource("application-" + env + ".properties")
};
Properties config = PropertiesLoaderUtils.loadProperties(resources);
8.3 自定义配置加载器
java
public class MyConfigLoader {
public Properties loadConfig(String... locations) {
Resource[] resources = Arrays.stream(locations)
.map(ClassPathResource::new)
.toArray(Resource[]::new);
try {
return PropertiesLoaderUtils.loadProperties(resources);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
九、局限性与注意事项
9.1 局限性
| 限制 | 说明 |
|---|---|
| 不支持 YAML/JSON | 仅处理标准 .properties 格式 |
| 无缓存机制 | 每次调用都重新加载(适合启动时加载) |
| 无占位符解析 | 仅加载原始键值,不处理 ${...} |
9.2 注意事项
- 路径标准化 :
Resource实现(如ClassPathResource)会处理路径,但建议使用正斜杠/; - 编码一致性:确保所有属性文件使用相同编码;
- 性能考量:避免在高频调用路径中使用(如每次 HTTP 请求)。
十、总结
PropertiesLoaderUtils 是 Spring 框架中一个简洁、可靠、实用的工具类。其核心价值在于:
- 统一属性加载 :基于
Resource抽象,支持任意资源位置; - 安全资源管理:通过 try-with-resources 防止泄漏;
- 编码灵活性:兼顾传统规范与现代 UTF-8 需求;
- 多资源配置:天然支持配置覆盖与合并;
- 作为基础设施:支撑 Spring 配置体系的底层实现。
| 维度 | 关键结论 |
|---|---|
| 核心方法 | loadProperties(Resource), fillProperties(..., encoding) |
| 编码默认值 | ISO-8859-1(符合 Java 规范) |
| 资源管理 | 自动关闭流,无泄漏风险 |
| 合并策略 | 后加载覆盖先加载 |
| 适用阶段 | 应用启动期配置加载 |
最终建议 :
在需要从资源加载
.properties文件的场景中,优先使用PropertiesLoaderUtils。它封装了 I/O 细节,提供了一致、安全、高效的 API,是 Spring "约定优于配置" 理念在配置加载领域的具体体现。