Spring 注入 Properties 详解
一、什么是 Properties 注入?
java.util.Properties 是 Java 标准库中用于存储键值对的类,它的键和值都是 String 类型。在 Spring 中,Properties 注入是指将一组配置以 Properties 对象的形式注入到 Bean 中。
Properties 与 Map 的区别在于:Properties 的键和值都是字符串,且它本身提供了 getProperty()、load()、store() 等操作方法,适合处理配置文件相关的场景。
java
@Component
public class MyBean {
private Properties configs; // 配置属性
private Properties mailConfig; // 邮件配置
}
二、XML 配置中注入 Properties
2.1 使用 <props> 标签
xml
<bean id="myBean" class="com.example.MyBean">
<property name="configs">
<props>
<prop key="timeout">30</prop>
<prop key="retry">3</prop>
<prop key="debug">true</prop>
</props>
</property>
</bean>
<props> 是 XML 中专门用于注入 Properties 的标签,等价于:
java
Properties configs = new Properties();
configs.setProperty("timeout", "30");
configs.setProperty("retry", "3");
configs.setProperty("debug", "true");
2.2 注入对象类型的值
如果需要注入非 String 类型的值,使用 <map> 而不是 <props>:
xml
<bean id="myBean" class="com.example.MyBean">
<property name="configs">
<map>
<entry key="timeout" value="30"/>
<entry key="maxSize" value="1024"/>
</map>
</property>
</bean>
<map> 允许键和值是任意类型,Spring 会进行类型转换。而 <props> 只支持 String 到 String。
2.3 引用外部 Properties 文件
通过 util: 命名空间加载外部 Properties 文件:
xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!-- 加载 classpath 下的 properties 文件 -->
<util:properties id="appConfig" location="classpath:app.properties"/>
<!-- 注入到 Bean 中 -->
<bean id="myBean" class="com.example.MyBean">
<property name="configs" ref="appConfig"/>
</bean>
</beans>
app.properties:
properties
app.name=myapp
app.timeout=30
app.debug=true
2.4 使用 PropertyPlaceholderConfigurer
老项目常用 PropertyPlaceholderConfigurer 加载 Properties 文件,并支持在 XML 中使用 ${} 占位符:
xml
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:app.properties</value>
<value>classpath:db.properties</value>
</list>
</property>
</bean>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
Spring Boot 中,这个类已被 PropertySourcesPlaceholderConfigurer 取代,自动配置。
三、注解方式注入 Properties
3.1 使用 @PropertySource 加载 Properties 文件
java
@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
@Autowired
private Environment environment;
@Bean
public Properties appConfig() {
Properties props = new Properties();
props.setProperty("app.name", environment.getProperty("app.name"));
props.setProperty("app.timeout", environment.getProperty("app.timeout"));
return props;
}
}
这种方式比较繁琐,需要逐个从 Environment 中读取。更好的方式是使用 @ConfigurationProperties 或 @Value。
3.2 使用 @Value 注入单个属性
java
@Component
@PropertySource("classpath:app.properties")
public class MyBean {
@Value("${app.name}")
private String appName;
@Value("${app.timeout:30}")
private int timeout;
}
@Value 适合单个属性的注入,不适合批量注入。
3.3 使用 @ConfigurationProperties 批量绑定
java
@Component
@ConfigurationProperties(prefix = "app")
@Getter
@Setter
public class AppProperties {
private String name;
private int timeout;
private boolean debug;
private Properties extra; // 嵌套 Properties
}
properties
# app.properties
app.name=myapp
app.timeout=30
app.debug=true
app.extra.key1=value1
app.extra.key2=value2
或者使用 YAML:
yaml
app:
name: myapp
timeout: 30
debug: true
extra:
key1: value1
key2: value2
@ConfigurationProperties 支持将配置绑定到 Properties 类型的字段,但要求配置的结构清晰。
四、直接注入 Properties Bean
在 Spring Boot 中,可以通过 @Bean 定义一个 Properties 类型的 Bean,然后注入到需要的地方。
4.1 定义 Properties Bean
java
@Configuration
public class PropertiesConfig {
@Bean("mailProperties")
public Properties mailProperties() {
Properties props = new Properties();
props.setProperty("mail.host", "smtp.example.com");
props.setProperty("mail.port", "587");
props.setProperty("mail.username", "noreply@example.com");
props.setProperty("mail.password", "secret");
return props;
}
}
4.2 注入使用
java
@Service
public class MailService {
@Autowired
@Qualifier("mailProperties")
private Properties mailProperties;
public void send(String to) {
String host = mailProperties.getProperty("mail.host");
String port = mailProperties.getProperty("mail.port");
// 发送邮件
}
}
如果容器中只有一个 Properties 类型的 Bean,可以省略 @Qualifier。如果有多个,必须通过名称区分。
4.3 从配置文件加载到 Properties Bean
java
@Configuration
public class AppConfig {
@Bean
public Properties appConfig() throws IOException {
Properties props = new Properties();
try (InputStream is = new ClassPathResource("app.properties").getInputStream()) {
props.load(is);
}
return props;
}
}
4.4 使用 @PropertySource + @Bean 组合
java
@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
@Autowired
private Environment environment;
@Bean
public Properties appProperties() {
Properties props = new Properties();
// 读取所有 app. 开头的配置
for (String key : Arrays.asList(
"app.name", "app.timeout", "app.debug")) {
String value = environment.getProperty(key);
if (value != null) {
props.setProperty(key, value);
}
}
return props;
}
}
这种方式适合需要把一组配置以 Properties 对象的形式传给第三方库的场景。
五、Properties 注入的底层原理
Spring 在注入 Properties 时,会根据目标类型选择合适的转换策略:
XML 配置 <props>:
Spring 解析 <props> 标签时,创建 Properties 对象,将 <prop> 中的键值对逐个 setProperty()。这是 Spring 内部直接构造的 Properties 实例。
@ConfigurationProperties 绑定:
如果字段类型是 Properties,Spring 的 PropertiesConfigurationFactory 或 Binder 会递归处理嵌套配置,将嵌套的键值对展平后存入 Properties 对象。
自动注入 Properties Bean:
如果容器中存在 Properties 类型的 Bean,@Autowired 按类型注入。Properties 本身是一个 Hashtable 的子类,Spring 将其作为普通 Bean 管理。
六、Properties vs Map vs @ConfigurationProperties
| 对比维度 | Properties | Map | @ConfigurationProperties |
|---|---|---|---|
| 键值类型 | 仅 String | 任意类型 | 任意类型 |
| 是否支持嵌套 | 展平为字符串键 | 支持嵌套 Map | 支持嵌套对象 |
| 类型安全 | 弱(需手动转换) | 中 | 强 |
| 适用场景 | 与传统 Properties 文件对接 | 简单键值对 | 结构化配置 |
| 配置文件绑定 | 需手动处理 | @ConfigurationProperties |
原生支持 |
选择建议:
- 需要将配置传递给第三方库(如 JavaMail、数据源)时,用 Properties
- 需要结构化、类型安全的配置时,用
@ConfigurationProperties - 简单的键值对且不需要嵌套时,用 Map 也可以
七、常见问题
7.1 XML 中 <props> 和 <map> 混用
xml
<!-- ❌ 错误:Properties 属性不能用 <map> 注入非 String 值 -->
<property name="configs">
<map>
<entry key="timeout" value="30"/>
</map>
</property>
如果属性类型是 Properties,应该用 <props>。<map> 适用于 Map 类型。
7.2 @ConfigurationProperties 绑定 Properties 失败
原因:Properties 的键必须是 String,如果配置中有复杂的嵌套结构,绑定可能失败。
解决 :对于复杂结构,改用 Map<String, Object> 或自定义对象。
7.3 多个 Properties Bean 注入冲突
原因 :容器中有多个 Properties 类型的 Bean,@Autowired 无法确定注入哪一个。
解决 :使用 @Qualifier("beanName") 指定具体名称。
java
@Autowired
@Qualifier("mailProperties")
private Properties mailProperties;
7.4 Properties 文件中的中文乱码
原因 :Properties.load() 默认使用 ISO-8859-1 编码。
解决 :使用 load(Reader) 并指定 UTF-8:
java
Properties props = new Properties();
try (Reader reader = new InputStreamReader(
new ClassPathResource("app.properties").getInputStream(),
StandardCharsets.UTF_8)) {
props.load(reader);
}
或者直接使用 @ConfigurationProperties,Spring Boot 会正确处理编码。
7.5 注入的 Properties 是只读的
原因 :@Value 注入的属性是只读的,无法动态修改。
解决 :如果需要动态修改配置,使用 @ConfigurationProperties 配合 @RefreshScope,或注入 Environment 手动获取。
八、最佳实践
优先使用 @ConfigurationProperties。 在 Spring Boot 项目中,结构化配置用 @ConfigurationProperties 绑定最自然,类型安全且支持嵌套。
需要传递 Properties 给第三方库时,用 Properties Bean。 比如 JavaMail 的 JavaMailSenderImpl、数据源的配置等,第三方库通常接受 Properties 参数。
避免用 @Value 批量注入。 如果有一组配置需要注入,用 @ConfigurationProperties 而不是多个 @Value。
注意 Properties 的编码。 使用 UTF-8 加载 Properties 文件,避免中文乱码。
多个 Properties Bean 用 @Qualifier 区分。 为不同的 Properties Bean 指定清晰的名称,注入时明确指定。
九、总结
| 维度 | 核心要点 |
|---|---|
| XML 注入 | 使用 <props> 标签,或用 <util:properties> 加载外部文件 |
| @Value 注入 | 适合单个属性,不适合批量注入 |
| @ConfigurationProperties | 批量绑定配置到对象,支持嵌套 Properties |
| Properties Bean | 通过 @Bean 定义,@Autowired + @Qualifier 注入 |
| 键值类型 | Properties 的键和值都是 String |
| 编码 | 使用 UTF-8 加载,避免中文乱码 |
| 多 Bean 冲突 | 使用 @Qualifier 指定名称 |
| 最佳实践 | 结构化配置用 @ConfigurationProperties,第三方库对接用 Properties Bean |
Properties 注入在 Spring 中主要用于两类场景:一是与传统 Properties 文件对接,二是将配置以 Properties 对象的形式传递给第三方库。在 Spring Boot 项目中,日常的配置管理更推荐用 @ConfigurationProperties,Properties 注入更多出现在需要兼容旧代码或对接第三方库的场景中。