@Value 注解
一句话定位:
@Value是 Spring Boot 的单个配置值注入器,通过${}占位符从外部配置源读取属性值并注入到字段,支持 SpEL 表达式和默认值语法。
定义与作用
@Value 用于将单个配置属性注入到 Bean 的字段、方法参数或构造器参数中。它解决了"硬编码配置值"的问题,让代码中的端口号、超时时间、系统名称等可变参数从外部读取。
对比传统 SSM 的繁琐过程
| 维度 | 传统 SSM | Spring Boot + @Value |
|---|---|---|
| 配置注入方式 | 在 XML 中写 <property name="timeout" value="5000"/> | 直接在字段上标注 @Value("${timeout:5000}") |
| 默认值支持 | 需要在 XML 中额外配置 default-value | 支持 ${key:default} 语法 |
| SpEL 支持 | 需要配置 SpEL 解析器 | 原生支持 #{} 表达式 |
| 代码侵入性 | 无注解,但依赖 XML | 一个注解即可,无 XML |
与 @ConfigurationProperties 的对比
| 维度 | @Value | @ConfigurationProperties |
|---|---|---|
| 绑定方式 | 单个属性,逐个注入 | 批量绑定,按前缀整体映射 |
| 类型支持 | 基础类型 + SpEL 表达式 | 支持复杂类型(List、Map、Duration) |
| 松散绑定 | 不支持(必须精确匹配键名) | 支持(max-pool-size → maxPoolSize) |
| 校验支持 | 无 | 可配合 @Validated + JSR-303 校验 |
| 适用场景 | 单个简单值(如端口、超时、开关) | 结构化配置组(如数据库连接参数) |
适用位置与常用属性
适用位置
@Value 可以标注在:
- 字段级别:直接注入到字段(最常用)
- 方法参数级别:注入到
@Bean方法的参数 - 构造器参数级别:配合
@Autowired构造器注入
// 方式一:字段注入(最常用)
@Value("${server.port:8080}")
private int serverPort;
// 方式二:方法参数注入
@Bean
public DataSource dataSource(@Value("${spring.datasource.url}") String url) {
// ...
}
// 方式三:构造器参数注入
@Component
public class SystemConfig {
private final String systemName;
public SystemConfig(@Value("${feixiang.student.system-name}") String systemName) {
this.systemName = systemName;
}
}
常用语法
| 语法 | 说明 | 示例 |
|---|---|---|
${key} | 读取配置值,必填 | @Value("${server.port}") |
${key:default} | 读取配置值,提供默认值 | @Value("${server.port:8080}") |
#{expression} | SpEL 表达式 | @Value("#{2 * 60}") |
${key} + #{expr} | 混合使用 | @Value("${app.name} #{T(java.time.LocalDate).now()}") |
核心原理
@Value 注入流程
图解释:@Value 的处理分为三个阶段。第一阶段解析 ${} 占位符,从 Environment 中查找对应的属性值;第二阶段解析 #{} 中的 SpEL 表达式;第三阶段通过 ConversionService 将字符串结果转换为目标字段类型(如 "8080" → int),最后通过反射注入到字段中。
类型转换机制
完整示例
场景说明
飞翔科技学生成绩管理系统的 application.yml 中定义了多项配置。后端开发小崔需要在 SystemConfig 类中读取这些配置,用于系统启动时的自检日志打印。要求:端口号有默认值、系统名称必须读取、超时时间使用 SpEL 计算。
操作前:配置值硬编码
// 操作前:配置硬编码在代码中,每次改配置都要重新编译
package com.feixiang.student.config;
import org.springframework.stereotype.Component;
@Component
public class LegacySystemConfig {
private int serverPort = 8080; // 写死
private String systemName = "飞翔科技学生成绩管理系统"; // 写死
private int requestTimeout = 2 * 60 * 1000; // 写死:2分钟
private boolean debugMode = false; // 写死
// 如果产品经理要求把超时改为3分钟,必须改代码重新部署
// 如果运维要求把端口改为80,必须改代码重新部署
}
痛点:所有配置值硬编码在 Java 源码中,任何修改都需要重新编译、打包、部署。
使用该注解的完整代码
步骤1:application.yml 配置
# application.yml
server:
port: 8080
feixiang:
student:
system-name: "飞翔科技学生成绩管理系统"
request-timeout-seconds: 120
debug-mode: false
allowed-origins: "http://localhost:3000"
步骤2:使用 @Value 注入配置
package com.feixiang.student.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class SystemConfig {
// 读取配置,带默认值:如果 application.yml 未配置,则使用 8080
@Value("${server.port:8080}")
private int serverPort;
// 读取配置,无默认值:如果 application.yml 未配置,启动报错
@Value("${feixiang.student.system-name}")
private String systemName;
// SpEL 表达式:读取秒数,转换为毫秒(用于兼容旧代码)
@Value("#{${feixiang.student.request-timeout-seconds:120} * 1000}")
private int requestTimeoutMs;
// 布尔值注入
@Value("${feixiang.student.debug-mode:false}")
private boolean debugMode;
// 字符串值注入
@Value("${feixiang.student.allowed-origins:*}")
private String allowedOrigins;
public void printStartupInfo() {
System.out.println("╔══════════════════════════════════════╗");
System.out.println("║ " + systemName + " ║");
System.out.println("╠══════════════════════════════════════╣");
System.out.println("║ 服务端口: " + serverPort + " ║");
System.out.println("║ 请求超时: " + requestTimeoutMs + " ms");
System.out.println("║ 调试模式: " + debugMode + " ║");
System.out.println("║ 允许跨域: " + allowedOrigins + " ║");
System.out.println("╚══════════════════════════════════════╝");
}
// Getters
public int getServerPort() { return serverPort; }
public String getSystemName() { return systemName; }
public int getRequestTimeoutMs() { return requestTimeoutMs; }
public boolean isDebugMode() { return debugMode; }
public String getAllowedOrigins() { return allowedOrigins; }
}
步骤3:启动验证
package com.feixiang.student;
import com.feixiang.student.config.SystemConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class StudentApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(StudentApplication.class, args);
SystemConfig config = ctx.getBean(SystemConfig.class);
config.printStartupInfo();
}
}
操作后运行结果及分析
默认启动(无外部参数)输出:
╔══════════════════════════════════════╗
║ 飞翔科技学生成绩管理系统 ║
╠══════════════════════════════════════╣
║ 服务端口: 8080 ║
║ 请求超时: 120000 ms
║ 调试模式: false ║
║ 允许跨域: http://localhost:3000 ║
╚══════════════════════════════════════╝
通过命令行覆盖后启动:
java -jar student-system.jar \
--server.port=9090 \
--feixiang.student.debug-mode=true
输出:
╔══════════════════════════════════════╗
║ 飞翔科技学生成绩管理系统 ║
╠══════════════════════════════════════╣
║ 服务端口: 9090 ║
║ 请求超时: 120000 ms
║ 调试模式: true ║
║ 允许跨域: http://localhost:3000 ║
╚══════════════════════════════════════╝
分析:
- 默认值生效:
server.port被命令行参数覆盖为 9090,而requestTimeoutMs仍使用默认值计算出的120000 - SpEL 计算:
#{${feixiang.student.request-timeout-seconds:120} * 1000}先读取配置值120,再乘以1000,得到120000 - 布尔值注入:
debugMode从false被外部参数覆盖为true - 无默认值强制读取:
systemName没有默认值,如果application.yml中未配置,启动时会抛出IllegalArgumentException
易错场景与面试考点
易错场景一:给复杂类型注入导致类型转换失败
小崔尝试用 @Value 注入一个列表:
// 错误示范:@Value 不支持直接注入 List
@Component
public class BadConfig {
@Value("${feixiang.student.features}")
private List<String> features; // ← 启动时抛出 ConversionFailedException
}
对应的 YAML:
feixiang:
student:
features:
- cache
- log
- monitor
后果:@Value 的 ConversionService 无法将 YAML 列表直接转换为 List<String>。它只能处理逗号分隔的字符串(如 "cache,log,monitor")。启动时抛出 ConversionFailedException。
正确做法:复杂类型(List、Map、嵌套对象)应使用 @ConfigurationProperties,而不是 @Value:
// 正确:使用 @ConfigurationProperties 绑定 List
@Component
@ConfigurationProperties(prefix = "feixiang.student")
public class FeixiangProperties {
private List<String> features; // 支持 List
// getter + setter
}
易错场景二:默认值语法写错位置
小崔写了一个错误的默认值表达式:
// 错误示范:默认值语法错误
@Value("${feixiang.student.timeout:3000:5000}")
private int timeout; // ← 解析失败
后果:Spring 的占位符解析器将 3000:5000 整体作为默认值,这不是一个合法的 int 值,启动时抛出 NumberFormatException。
正确做法:${key:default} 中只有一个冒号分隔键和默认值:
@Value("${feixiang.student.timeout:3000}")
private int timeout;
面试考点
Q:@Value 和 @ConfigurationProperties 的核心区别是什么?
@Value适合单个简单属性的注入,支持 SpEL 表达式和默认值,但不支持松散绑定和复杂类型;@ConfigurationProperties适合批量结构化配置,支持松散绑定、List/Map、嵌套对象,可配合@Validated校验。配置项超过3个或有嵌套结构时,优先用@ConfigurationProperties。
Q:@Value 的默认值语法是什么?如果未配置会怎样?
语法是
${key:default}。如果未配置且无默认值,启动时抛出IllegalArgumentException: Could not resolve placeholder 'key'。
Q:@Value 支持哪些类型?
支持所有 Spring
ConversionService能转换的类型:基本类型(int、long、boolean、String)、包装类、Enum、Duration、DataSize、URI 等。但不支持直接注入 List、Map 或嵌套对象。
Q:@Value 的 SpEL 表达式能做什么?
#{}内可以写 Spring Expression Language 表达式,例如:
- 算术运算:
#{2 * 60}- 读取其他 Bean 属性:
#{@otherBean.property}- 调用静态方法:
#{T(java.lang.Math).random()}- 三元表达式:
#{${app.enabled} ? '开启' : '关闭'}