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 中注意 ${}、#{} 和 : 的语法含义。理解这些规则,就能避免绝大多数注入异常。

相关推荐
桃西西呀10 小时前
Spring AI Alibaba 之二:Spring AI 底座与原子抽象一笔带过,看清 SAA 在之上加了什么编排
人工智能·spring·llm
颜进强10 小时前
23 · NestJs ModuleRef 模块引用:容器递给你的"取货窗口",四个 API 四种语义
前端·后端·ai编程
captain37610 小时前
Maven
java·后端·idea
hsfxuebao11 小时前
Loop Engineering 已死? 一文带你了解Graph Engineering
人工智能·后端
liangshanbo121511 小时前
主系统登录后,子系统怎么实现自动登录?
java·网络·数据库
颜进强11 小时前
22 · NestJs InjectionScopes 注入作用域:默认单例不是偷懒,是最优——以及何时才该打破
前端·后端·ai编程
ShineWinsu11 小时前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热
rannn_11111 小时前
【Java面试题】高频面试题1|Java 后端、MySQL、Redis 与 RAG 面试整理
java·jvm·数据库·后端·面试
写后端的胖头鱼11 小时前
一文详解Spring 事务失效场景
java·spring·事务·transactional·事务传播机制
Q264336502311 小时前
【有源码】基于Spring Boot和Vue的在线考试与交流系统-教育信息化背景下在线考试与交流一体化平台
java·vue.js·spring boot·mysql·spring·毕业设计·课程设计