Spring ${} vs #{} 语法全景图
一句话记忆:
${} 是 占位符 (静态字符串替换),#{} 是 SpEL 表达式 (运行时计算)。
二者可以 嵌套 、联动 ,带来 声明式动态能力。
一、基础区别
维度 |
${} |
#{} |
名称 |
Property Placeholder |
SpEL(Spring Expression Language) |
求值时机 |
容器启动时 替换 |
运行时 解析 |
数据来源 |
Environment、PropertySource |
Bean、系统属性、运算、方法调用 |
常见用途 |
读取 application.yml |
计算、条件、集合、Bean 调用 |
示例 |
${server.port} |
#{T(java.lang.Math).random()} |
默认值 |
${key:default} |
#{key ?: default} |
二、基础用法对比
1️⃣ 读取配置
yaml
复制代码
my:
name: Tom
age: 18
java
复制代码
@Value("${my.name}") // Tom
private String name;
@Value("${my.age}") // 18(字符串)
private int age;
@Value("${unknown:default}") // default
private String fallback;
2️⃣ 运行时计算
java
复制代码
@Value("#{T(java.lang.Math).random() * 100}")
private double rand100;
@Value("#{systemProperties['os.name']}")
private String os;
三、混合嵌套(经典技巧)
java
复制代码
@Value("#{${feature.price} * (1 + ${feature.tax})}")
private BigDecimal finalPrice;
${feature.price}
先被替换为 100
,表达式变为 #{100 * (1 + 0.2)}
→ 120
。
四、高级用法速查表
场景 |
代码示例 |
三目/Elvis |
"#{flag ? 'A' : 'B'}" / "#{name ?: 'NO_NAME'}" |
集合索引 |
"${list[0]}" / "#{map['key']}" |
集合过滤 & 投影 |
"#{users.?[age > 18].![name]}" |
正则匹配 |
"#{email matches '^[\\w\\.-]+@(.+)$'}" |
Bean 调用 |
"#{priceService.current() * 1.1}" |
静态方法 |
"#{T(java.time.LocalDate).now()}" |
条件 Bean 注册 |
@ConditionalOnExpression("#{env['spring.profiles.active'] == 'prod'}") |
五、XML / 注解 / 编程式 全场景
场景 |
写法 |
XML |
<property name="timeout" value="#{${timeout} * 1000}"/> |
@Bean |
@Bean @ConditionalOnExpression("#{systemProperties['debug'] != null}") |
编程式 |
ExpressionParser parser = new SpelExpressionParser(); |
六、常见误区 & 避坑
坑 |
说明 |
${} 不支持运算 |
${1+2} 会原样输出,不会被计算 |
#{} 在 application.yml 无效 |
只能在 @Value 、XML 、@Conditional 等场景使用 |
构造器注入 无法实时刷新 |
使用 @ConfigurationProperties 或字段注入 |
默认值写法 |
${key:default} vs #{key ?: default} |
七、一句话总结
${} 负责"占位",#{} 负责"计算";
二者嵌套即可实现声明式动态配置,让 Spring 在运行时依旧"活"起来。