Spring 注入 Properties 详解

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 的 PropertiesConfigurationFactoryBinder 会递归处理嵌套配置,将嵌套的键值对展平后存入 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 注入更多出现在需要兼容旧代码或对接第三方库的场景中。

相关推荐
星空1 小时前
Map<String, String>`Map`是接口,不能直接 new
java·前端·算法
liangbo71 小时前
12-JVM 调优方法论与参数速查
java·jvm
干到60岁退休的码农1 小时前
16.过滤器中处理异常响应返回
java·spring boot·mybatis
shirsl1 小时前
算法 Day3-队列 / deque + 链表
数据结构·python·算法·链表
Java_2017_csdn1 小时前
StringUtils.hasText() 和 StringUtils.isNotBlank() 方法对比
java
Joy T1 小时前
Spring AI 2.0 进阶入门:RAG、Structured Output 与 Agent 信息闭环
java·人工智能·spring·rag·springai·agent入门
名字还没想好☜1 小时前
Python 的 __call__ 实战:让实例像函数一样被调用,做带状态计数器、缓存器与可配置策略
开发语言·后端·python·缓存·编程语言
xiaoqiMikko2 小时前
jackson-databind 又出 4 条,Dependabot 一条都不报:2.21.5 还差 3 条
java·安全
cpolar技术支持2 小时前
AI Agent 跑半小时就忘目标?用检查点与任务账本做可恢复长任务,cpolar 分享只读时间线
python·sqlite·cpolar·ai agent·任务恢复