这两个符号虽然长得像,但在 Java 开发(特别是 Spring 和 MyBatis)中,它们的功能、执行时机和安全性有着天壤之别。
简单来说:
${}是字符串替换(简单粗暴,直接拼接)。#{}是参数占位 或表达式解析(安全智能,预编译或计算)。
为了让你更清晰地理解,我们需要分两个最常见的场景来看:Spring 框架 (配置/注解)和 MyBatis(数据库 SQL)。
1. 在 Spring 框架中(@Value, SpEL)
在 Spring 的配置文件或 @Value 注解中,区别在于**"静态文本"与"动态计算"**。
| 特性 | ${} (属性占位符) |
#{} (SpEL 表达式) |
|---|---|---|
| 全称 | Property Placeholder | Spring Expression Language |
| 作用 | 读取配置文件(如 application.yml)中的值。 | 执行代码逻辑、数学运算、调用 Bean 方法。 |
| 执行时机 | Spring 容器启动时解析。 | 运行时(Runtime)动态解析。 |
| 能力 | 只能获取字符串,不支持运算。 | 支持加减乘除、三目运算、调用静态方法等。 |
| 示例 | @Value("${server.port}") |
@Value("#{T(Math).random()}") |
代码对比:
java
// 1. ${}:纯粹的值读取
// 从配置文件读取 app.name 的值,如果是字符串 "MyApp",则注入 "MyApp"
@Value("${app.name}")
private String name;
// 2. #{}:强大的逻辑计算
// 调用 Math 类的静态方法生成随机数
@Value("#{T(java.lang.Math).random() * 100}")
private double randomNum;
// 3. 混合使用(高阶玩法)
// 先通过 ${} 读取配置值 "100",再通过 #{} 进行乘法运算
@Value("#{${app.timeout} * 2}")
private int doubleTimeout;
2. 在 MyBatis 中(SQL 语句)
在写 SQL 时,区别在于**"安全性"**。这是面试和实际开发中最容易踩坑的地方。
| 特性 | #{} (预编译占位符) |
${} (字符串替换) |
|---|---|---|
| 原理 | 使用 JDBC 的 PreparedStatement。 |
简单的字符串拼接。 |
| 安全性 | 高(防止 SQL 注入)。 | 低(存在 SQL 注入风险)。 |
| 表现形式 | 数据库看到的是 ?,会自动加上引号。 |
数据库看到的是拼接后的完整 SQL。 |
| 适用场景 | 绝大多数参数传递(如 WHERE id = #{id})。 |
动态表名、动态排序字段(如 ORDER BY ${column})。 |
SQL 示例:
-
#{}(安全):xml<!-- 传入 "123" 或 "123' OR '1'='1" 都会被当作普通字符串处理 --> select * from user where id = #{id}编译后 SQL:
select * from user where id = ?(参数被安全设置) -
${}(危险):xml<!-- 传入恶意字符串会导致 SQL 逻辑被篡改 --> select * from user where id = ${id}编译后 SQL:
select * from user where id = 123(直接拼接)
3. 总结与建议
- 在 Spring 中:
- 读配置文件用
${}。 - 做运算、调方法用
#{}。
- 读配置文件用
- 在 MyBatis 中:
- 永远优先使用
#{}。 - 只有当你要动态传入数据库表名 、列名 (如
ORDER BY后面的字段)时,才被迫使用${}(此时要注意防范 SQL 注入,比如做白名单校验)。
- 永远优先使用