人人都会AI编程

@Value + @PropertySource 注入配置值

更新时间:2026-07-11

在 Spring 应用中,将环境相关的配置(数据库连接、外部服务地址、功能开关等)硬编码在源码中是大忌。Spring 提供了 @PropertySource@Value 这一经典组合,使得我们可以将配置外部化到 .properties 文件中,再通过字段或方法参数注入到 Bean 里。这种方案简洁、直接,在中小型项目和传统 Spring MVC 应用中极为常见。

5.4.1 基本用法

1. 准备配置文件

假设在类路径下创建 application.properties 或自定义的 app.properties 文件:

# app.properties
sms.api.url=https://api.sms-provider.com/v2
sms.api.key=your-api-key-here
sms.timeout=5000
sms.retry.max=3

2. 使用 @PropertySource 引入配置文件

在任意一个 @Configuration 类或 @Component 类上添加 @PropertySource 注解,指向对应的配置文件。Spring 会将其中的键值对加载到运行时的 Environment 中。

@Configuration
@PropertySource("classpath:app.properties")
public class AppConfig {
    // 其他配置
}

更常见的做法是直接在 Spring Boot 应用的启动类或某个配置类上统一声明。如果是 Spring Boot,application.properties 会被自动加载,无须显式声明;但自定义名称的文件则需要 @PropertySource

3. 使用 @Value 注入属性值

在需要配置值的 Bean 中,使用 @Value 注解配合 ${...} 占位符语法,即可将配置文件中的值注入到字段、构造器参数或 setter 方法中:

@Service
public class SmsService {

    @Value("${sms.api.url}")
    private String apiUrl;

    @Value("${sms.api.key}")
    private String apiKey;

    private final int timeout;
    private final int maxRetry;

    // 构造器注入,用 @Value 标注参数
    public SmsService(@Value("${sms.timeout}") int timeout,
                      @Value("${sms.retry.max}") int maxRetry) {
        this.timeout = timeout;
        this.maxRetry = maxRetry;
    }

    public void send(String phone, String content) {
        // 使用 apiUrl、apiKey、timeout 等配置
    }
}

注入时机发生在 Bean 初始化的过程中,所有 ${} 占位符都会被替换为 Environment 中对应的值。如果找不到对应的 key,容器启动时就会抛出 IllegalArgumentException

5.4.2 设置默认值,避免启动失败

很多时候我们希望在配置缺失时提供一个合理的默认值,而不是直接启动失败。在 @Value 表达式中可以用 ${key:default_value} 的语法来指定默认值:

@Value("${sms.timeout:3000}")
private int timeout;   // 若未配置 sms.timeout,则使用 3000

@Value("${sms.retry.max:3}")
private int maxRetry;

这样即使配置文件里没有写相关的 key,应用也能正常启动。默认值可以是字符串、数字甚至 SpEL 表达式,但要注意类型匹配。

5.4.3 注入列表、Map 等复杂类型

虽然 @Value 主要处理简单字符串,但通过与 SpEL 的结合,也能注入简单的集合类型。

  • 注入数组或 List(逗号分隔):
sms.white.list=13800001111,13900002222,18600003333
@Value("${sms.white.list}")
private String[] whiteListArray;

// 或者直接注入 List(使用 SpEL 转换)
@Value("#{'${sms.white.list}'.split(',')}")
private List<String> whiteList;
  • 注入键值对(Map):

属性文件无法直接表达复杂的 Map 结构,但可以通过自定义格式后手动解析。例如:

sms.template.A=您的验证码是{code}
sms.template.B=您的订单{orderId}已发货

然后编写一个 @ConfigurationProperties 的组件会更合适(下一节会介绍)。@Value 不太擅长处理这类场景,强行使用会让代码变得晦涩。

5.4.4 使用 SpEL 在 @Value 中实现更灵活的注入

@Value 支持 SpEL 表达式,可以使用 #{} 语法引用其他 Bean 的属性、调用静态方法、执行运算等。

// 引用其他 Bean 的属性
@Value("#{appConfig.defaultTimeout}")
private int timeout;

// 调用静态方法
@Value("#{T(java.lang.Math).random() * 100}")
private double randomValue;

// 读取系统属性
@Value("#{systemProperties['user.dir']}")
private String userDir;

// 多 profile 下的条件取值
@Value("#{environment['active.profile'] == 'prod' ? 'production-key' : 'dev-key'}")
private String apiKey;

需要注意,过度使用 SpEL 会让配置变得难以追踪。@Value 的主要价值在于外部化配置,SpEL 表达式应作为辅助手段谨慎使用。

5.4.5 与 @PropertySource 配合的注意事项

1. 文件编码与中文处理

默认情况下,@PropertySource 使用 ISO 8859-1 编码读取 .properties 文件,如果文件中包含中文,可能会产生乱码。解决方案:

  • 将中文转为 Unicode 转义符(如 sms.content=\u60a8\u7684\u9a8c\u8bc1\u7801),但这可读性很差。
  • 使用 YAML 配置文件,并配合 @ConfigurationProperties 或 Spring Boot 的自动读取(YAML 默认 UTF-8)。
  • @PropertySource 中显式指定编码(Spring 4.3+ 支持):@PropertySource(value = "classpath:app.properties", encoding = "UTF-8")

2. 多文件加载与优先级

可以在 @PropertySource 中用数组加载多个配置文件:

@PropertySource({"classpath:common.properties", "classpath:app.properties"})

如果有相同的 key,后加载的会覆盖先加载的。若要实现更灵活的覆盖策略(如外部文件覆盖内部配置),可以使用 Spring Boot 的 application-{profile}.properties 或结合 Environment 后处理器。

3. 属性占位符嵌套

@PropertySource 声明中可以使用 ${} 引用已加载的属性,实现动态文件路径:

@PropertySource("classpath:config/${env.type:dev}/database.properties")

这要求 ${env.type} 在加载此文件之前就已经存在于 Environment 中,可以通过命令行参数、系统属性或 Spring Boot 的特性来提供。

5.4.6 何时使用 @Value 和 @PropertySource

@Value + @PropertySource 是一对非常轻量的配置注入方案,特别适合:

  • 小而独立的配置项:几个外部 URL、密钥、开关值。
  • 传统 Spring 项目或非 Boot 项目:没有自动加载 application.properties 能力时。
  • 需要默认值或简单的类型转换

但对于大量关联配置(如一个模块下有一组属性),@Value 会导致字段散落各处,维护困难。此时更推荐使用 @ConfigurationProperties 将一组配置映射到一个专门的 POJO 中,类型安全且支持 JSR-303 校验。下一节将详细介绍这一方式。

在实际工作中,一个典型的使用场景是:在 Spring Security 或第三方客户端配置类中,用 @Value 注入少量关键参数;而在业务模块如支付、短信等,通常使用 @ConfigurationProperties 进行统一管理。两种方式相辅相成,掌握它们的选择原则,能让你在处理配置时既灵活又不失整洁。