Spring 注入值中含有特殊符号的处理详解
一、为什么特殊符号会导致问题?
Spring 在注入值时,会经过多层解析:
- XML 解析 :
<、>、&等是 XML 语法字符 - 属性占位符解析 :
${}是占位符语法,:是默认值分隔符 - SpEL 表达式解析 :
#{}是表达式语法 - 类型转换 :
,可能被当作集合分隔符 - YAML 解析 :
:、#、{、}等是 YAML 语法字符
每一层都可能把特殊符号"吃掉"或误解析,导致注入结果与预期不符。
二、XML 配置中的特殊符号
2.1 XML 转义字符
XML 中有五个预定义实体,必须转义:
| 特殊符号 | 转义写法 | 说明 |
|---|---|---|
< |
< |
小于号 |
> |
> |
大于号 |
& |
& |
和号 |
" |
" |
双引号 |
' |
' |
单引号 |
xml
<!-- ❌ 错误:& 未转义 -->
<property name="url" value="jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC"/>
<!-- ✅ 正确:& 转义为 & -->
<property name="url" value="jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC"/>
2.2 使用 CDATA 避免转义
如果值中包含大量特殊字符,用 <![CDATA[...]]> 包裹:
xml
<property name="sql">
<value><![CDATA[
SELECT * FROM user WHERE age > 18 AND name != 'test'
]]></value>
</property>
CDATA 中的内容不会被 XML 解析器解析,>、<、& 都可以直接写。
2.3 值中含双引号或单引号
xml
<!-- 值中含双引号:外层用单引号 -->
<property name="message" value='He said "Hello"'/>
<!-- 值中含单引号:外层用双引号 -->
<property name="message" value="It's a test"/>
三、@Value 中的特殊符号
3.1 值中含 $ 符号
$ 在 @Value 中用于 ${} 占位符。如果值本身包含 $,需要转义。
java
// 配置文件:app.price=$100
// ❌ 错误:Spring 会把 $100 当作占位符
@Value("${app.price}")
// ✅ 正确:配置文件中转义,或用默认值
在 properties 文件中:
properties
app.price=$100
在 @Value 中,$ 本身不需要转义,但如果配置文件中写 ${app.price} 这种形式,$ 会被解析:
java
// 如果配置值就是字面量 "$100"
@Value("${app.price}")
private String price; // 注入 "$100",正常
如果值中包含 ${ 这样的组合,需要用 \ 转义:
java
@Value("${app.template:\\${name}}")
private String template; // 注入 "${name}"
3.2 值中含 # 符号
# 在 @Value 中用于 #{} SpEL 表达式。如果值本身包含 #,需要处理。
yaml
app:
color: "#FF0000"
java
// ❌ 错误:Spring 会把 #FF0000 当作 SpEL
@Value("${app.color}")
// ✅ 正确:配置值中的 # 本身不会触发 SpEL,因为 # 只在 #{} 中才触发
@Value("${app.color}")
private String color; // 注入 "#FF0000",正常
只有当 # 后面紧跟 { 时才会触发 SpEL 解析。单独的 # 没问题。
3.3 值中含 : 符号
: 在 @Value 中用于分隔配置键和默认值。如果配置值本身包含 :,需要注意。
java
// 配置文件:app.url=jdbc:mysql://localhost:3306/db
// ✅ 正常,因为 : 在配置值中,不在占位符语法中
@Value("${app.url}")
private String url; // 注入 "jdbc:mysql://localhost:3306/db"
但如果写默认值时:
java
// ❌ 错误:Spring 会把 jdbc 当作默认值,后面的内容被忽略
@Value("${app.url:jdbc:mysql://localhost:3306/db}")
private String url;
// ✅ 正确:用 SpEL 或拆分配置
@Value("${app.url:}")
private String url;
3.4 值中含逗号 ,
逗号在集合注入中会被当作分隔符。
yaml
app:
servers: server1,server2,server3
java
@Value("${app.servers}")
private List<String> servers; // 自动拆分为 3 个元素
如果值本身包含逗号:
yaml
app:
message: "hello, world"
java
// ❌ 错误:会被拆分成 "hello" 和 " world" 两个元素
@Value("${app.message}")
private List<String> messages;
// ✅ 正确:用 String 接收
@Value("${app.message}")
private String message;
3.5 值中含反斜杠 \
yaml
app:
path: "C:\\Users\\admin"
java
@Value("${app.path}")
private String path; // 注入 "C:\Users\admin"
在 YAML 中,双引号内的 \ 是转义字符,需要写 \\。单引号内不转义:
yaml
app:
path: 'C:\Users\admin' # 单引号内原样输出
四、YAML 中的特殊符号
4.1 : 的处理
YAML 中 : 是键值分隔符。如果值中含 :,需要加引号。
yaml
# ❌ 错误:值中的 : 可能被解析为新的键值对
app:
url: jdbc:mysql://localhost:3306/db
# ✅ 正确:加引号
app:
url: "jdbc:mysql://localhost:3306/db"
4.2 # 的处理
# 在 YAML 中是注释符。如果值中含 #,需要加引号。
yaml
# ❌ 错误:# 后面被当作注释
app:
color: #FF0000
# ✅ 正确:加引号
app:
color: "#FF0000"
4.3 { 和 } 的处理
{} 在 YAML 中表示内联 Map。如果值中含 {},需要加引号。
yaml
# ❌ 错误:被解析为 Map
app:
template: {name}
# ✅ 正确:加引号
app:
template: "{name}"
4.4 [ 和 ] 的处理
[] 在 YAML 中表示列表。如果值中含 [],需要加引号。
yaml
# ❌ 错误:被解析为列表
app:
pattern: [a-z]+
# ✅ 正确:加引号
app:
pattern: "[a-z]+"
4.5 & * ! | > 的处理
这些都是 YAML 的特殊字符,值中含这些字符时建议加引号。
yaml
app:
expr: "a & b"
alias: "*ref"
tag: "!important"
multi: "|line"
folded: ">text"
五、Properties 文件中的特殊符号
5.1 \ 转义
Properties 文件中,\ 是转义字符:
properties
# \ 需要写成 \\
app.path=C:\\Users\\admin
# : 和 = 在键中需要转义
app\:key=value
# 空格在值末尾需要转义
app.name=hello\
5.2 Unicode 转义
Properties 文件默认使用 ISO-8859-1 编码,中文需要转义:
properties
app.name=\u5f20\u4e09
或者使用 UTF-8 编码加载(Spring Boot 默认支持)。
5.3 $ 的处理
properties
app.price=$100
在 @Value("${app.price}") 中,$100 会正常注入,因为 $ 单独出现不会触发占位符解析。
六、SpEL 表达式中的特殊符号
6.1 字符串中的单引号
SpEL 中字符串用单引号表示。如果字符串本身含单引号,需要转义:
java
@Value("#{'It\\'s a test'}")
private String message; // "It's a test"
6.2 字符串中的双引号
java
@Value("#{'He said \"Hello\"'}")
private String message;
6.3 值的拼接
java
@Value("#{'${app.name}' + '-' + '${app.version}'}")
private String fullName;
七、完整示例:数据库连接 URL
数据库 URL 是典型的含特殊符号的配置,包含 :、/、?、&、= 等。
YAML 写法:
yaml
spring:
datasource:
url: "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"
username: root
password: "p@ssw0rd#123"
- URL 加引号,避免
&和:被误解析 - 密码含
#,必须加引号 - 密码含
@,YAML 中@不是特殊字符,但加引号更安全
properties 写法:
properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=p@ssw0rd#123
Properties 文件中 &、#、@ 不需要转义(# 在行首才是注释)。
XML 写法:
xml
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="p@ssw0rd#123"/>
</bean>
XML 中 & 必须转义为 &。
八、各场景特殊符号处理速查表
| 特殊符号 | XML | YAML | Properties | @Value | SpEL |
|---|---|---|---|---|---|
& |
& |
建议加引号 | 直接写 | 直接写 | 直接写 |
< |
< |
建议加引号 | 直接写 | 直接写 | 直接写 |
> |
> |
建议加引号 | 直接写 | 直接写 | 直接写 |
" |
" 或外层用单引号 |
外层用单引号 | 直接写 | 直接写 | \" |
' |
' 或外层用双引号 |
外层用双引号 | 直接写 | 直接写 | \' |
: |
直接写 | 建议加引号 | 键中需转义 | 默认值分隔符 | 直接写 |
# |
直接写 | 必须加引号 | 行首是注释 | 直接写 | #{} 触发表达式 |
$ |
直接写 | 直接写 | 直接写 | ${} 触发占位符 |
直接写 |
{ } |
直接写 | 必须加引号 | 直接写 | ${} 语法 |
#{} 语法 |
[ ] |
直接写 | 必须加引号 | 直接写 | 直接写 | 直接写 |
, |
直接写 | 直接写 | 直接写 | 集合分隔符 | 直接写 |
\ |
直接写 | 双引号内 \\ |
\\ |
转义符 | \\ |
% |
直接写 | 直接写 | 直接写 | 直接写 | 取模运算符 |
九、最佳实践
YAML 中值一律加引号。 这是最省心的做法。加引号后,YAML 不再解析值中的特殊字符,原样传递。
yaml
app:
url: "jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC"
password: "p@ssw0rd#123"
pattern: "[a-z]+"
template: "{name}"
XML 中注意五个预定义实体。 &、<、>、"、' 必须转义,或用 CDATA 包裹。
@Value 中的默认值含 : 时,用 SpEL 处理。 因为 : 是默认值分隔符,直接写会有歧义。
java
// ❌ 有歧义
@Value("${app.url:jdbc:mysql://localhost/db}")
// ✅ 用 SpEL
@Value("#{'${app.url:}' ?: 'jdbc:mysql://localhost/db'}")
密码等敏感值始终加引号。 密码中可能包含 #、@、$、! 等字符,加引号可以避免解析问题。
配置值中的逗号用 String 接收。 如果需要保持逗号不被拆分,用 String 类型接收,再手动处理。
十、总结
| 场景 | 核心问题 | 解决方案 |
|---|---|---|
XML 中的 &、<、> |
XML 语法冲突 | 转义为 &、<、> 或用 CDATA |
YAML 中的 :、#、{}、[] |
YAML 语法冲突 | 值加引号 |
Properties 中的 \ |
转义字符 | 写 \\ |
@Value 默认值中的 : |
分隔符冲突 | 用 SpEL 表达式处理 |
@Value 中的 $、# |
占位符/SpEL 语法 | 单独出现不触发,${ 和 #{ 需转义 |
集合注入中的 , |
被当作分隔符 | 改用 String 接收后手动拆分 |
| SpEL 中的引号 | 字符串边界冲突 | 用 \' 或 \" 转义 |
处理特殊符号的核心原则是:知道每一层解析器的语法规则,在对应层级做正确的转义或加引号 。YAML 中加引号是最简单有效的做法;XML 中注意预定义实体和 CDATA;@Value 中注意 ${}、#{} 和 : 的语法含义。理解这些规则,就能避免绝大多数注入异常。