Spring 注入值中含有特殊符号的处理详解

Spring 注入值中含有特殊符号的处理详解

一、为什么特殊符号会导致问题?

Spring 在注入值时,会经过多层解析:

  1. XML 解析<>& 等是 XML 语法字符
  2. 属性占位符解析${} 是占位符语法,: 是默认值分隔符
  3. SpEL 表达式解析#{} 是表达式语法
  4. 类型转换, 可能被当作集合分隔符
  5. YAML 解析:#{} 等是 YAML 语法字符

每一层都可能把特殊符号"吃掉"或误解析,导致注入结果与预期不符。

二、XML 配置中的特殊符号

2.1 XML 转义字符

XML 中有五个预定义实体,必须转义:

特殊符号 转义写法 说明
< &lt; 小于号
> &gt; 大于号
& &amp; 和号
" &quot; 双引号
' ' 单引号
xml 复制代码
<!-- ❌ 错误:& 未转义 -->
<property name="url" value="jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC"/>

<!-- ✅ 正确:& 转义为 &amp; -->
<property name="url" value="jdbc:mysql://localhost:3306/db?useSSL=false&amp;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&amp;serverTimezone=Asia/Shanghai&amp;characterEncoding=utf8"/>
    <property name="username" value="root"/>
    <property name="password" value="p@ssw0rd#123"/>
</bean>

XML 中 & 必须转义为 &amp;

八、各场景特殊符号处理速查表

特殊符号 XML YAML Properties @Value SpEL
& &amp; 建议加引号 直接写 直接写 直接写
< &lt; 建议加引号 直接写 直接写 直接写
> &gt; 建议加引号 直接写 直接写 直接写
" &quot; 或外层用单引号 外层用单引号 直接写 直接写 \"
' ' 或外层用双引号 外层用双引号 直接写 直接写 \'
: 直接写 建议加引号 键中需转义 默认值分隔符 直接写
# 直接写 必须加引号 行首是注释 直接写 #{} 触发表达式
$ 直接写 直接写 直接写 ${} 触发占位符 直接写
{ } 直接写 必须加引号 直接写 ${} 语法 #{} 语法
[ ] 直接写 必须加引号 直接写 直接写 直接写
, 直接写 直接写 直接写 集合分隔符 直接写
\ 直接写 双引号内 \\ \\ 转义符 \\
% 直接写 直接写 直接写 直接写 取模运算符

九、最佳实践

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 语法冲突 转义为 &amp;&lt;&gt; 或用 CDATA
YAML 中的 :#{}[] YAML 语法冲突 值加引号
Properties 中的 \ 转义字符 \\
@Value 默认值中的 : 分隔符冲突 用 SpEL 表达式处理
@Value 中的 $# 占位符/SpEL 语法 单独出现不触发,${#{ 需转义
集合注入中的 , 被当作分隔符 改用 String 接收后手动拆分
SpEL 中的引号 字符串边界冲突 \'\" 转义

处理特殊符号的核心原则是:知道每一层解析器的语法规则,在对应层级做正确的转义或加引号 。YAML 中加引号是最简单有效的做法;XML 中注意预定义实体和 CDATA;@Value 中注意 ${}#{}: 的语法含义。理解这些规则,就能避免绝大多数注入异常。

相关推荐
user_admin_god1 小时前
第 11 篇:实践三 —— 表单 / 合同字段抽取
java·人工智能·spring boot·语言模型
未秃头的程序猿1 小时前
全链路追踪落地一年:从翻日志到秒级定位,我们都做了什么
java·后端·面试
回家路上绕了弯1 小时前
ZCode 入门教程:从打开项目到完成一次 Java 代码修改
后端
薛定谔的算法1 小时前
M03:面向对象编程(OOP)
后端
SamDeepThinking1 小时前
new Thread()之后发生了什么?
java·后端·面试
亦暖筑序1 小时前
AgentScope Java 实战:Agent 的状态存在哪、怎么恢复、怎么隔离?
人工智能·后端·agent
斯维赤1 小时前
Spring AI | Function Calling 是什么?
java·后端
devpotato1 小时前
限流、熔断、降级:三者核心区别一文讲清
java
不灭的黄金瞳1231 小时前
Java数据类型与变量
java·开发语言·intellij-idea