1. XML 是什么
XML 的全称是 Extensible Markup Language,中文通常翻译为"可扩展标记语言"。
它是一种用于描述、存储和传输结构化数据的文本格式。XML 本身不会规定必须使用哪些标签,而是允许开发者根据业务需要定义标签。
例如,一份用户信息可以表示为:
xml
<?xml version="1.0" encoding="UTF-8"?>
<user>
<id>10001</id>
<name>张三</name>
<email>zhangsan@example.com</email>
</user>
这里的 <user>、<id>、<name> 和 <email> 都是开发者自行定义的标签。
XML 的核心目标不是"展示页面",而是:
- 描述结构化数据;
- 在不同系统之间交换数据;
- 保存应用程序配置;
- 表达层次关系;
- 为其他标记语言提供基础。
HTML 主要用于展示内容,而 XML 主要用于描述数据。
2. XML 的主要特点
2.1 标签可以自定义
XML 不像 HTML 那样预先定义了 <div>、<p>、<table> 等标签。
开发者可以根据业务定义自己的标签:
xml
<book>
<title>深入理解 Java 虚拟机</title>
<author>周志明</author>
<price>129.00</price>
</book>
2.2 数据具有层次结构
XML 天然适合表达父子关系:
xml
<company>
<department name="研发部">
<employee>
<name>张三</name>
</employee>
<employee>
<name>李四</name>
</employee>
</department>
</company>
2.3 具有良好的可读性
XML 本质上是纯文本,人和程序都可以读取。
2.4 跨平台、跨语言
Java、C#、Python、JavaScript、Go 等语言都支持 XML。
一个 Java 系统生成的 XML 文件,可以交给其他语言编写的系统解析。
2.5 支持严格的数据校验
XML 可以通过 DTD 或 XSD 定义文档结构,例如:
- 必须包含哪些元素;
- 元素出现的顺序;
- 某个元素可以出现几次;
- 元素的数据类型;
- 属性是否必填;
- 字符串长度和数值范围。
2.6 格式相对冗长
XML 的开始标签和结束标签都会占用空间:
xml
<name>张三</name>
相比 JSON:
json
{
"name": "张三"
}
XML 通常更加冗长,但它在命名空间、文档混合内容、结构校验等方面仍有独特优势。
3. XML 的基本结构
一份完整的 XML 文档通常包含:
xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>内容</child>
</root>
主要分为两部分:
- XML 声明;
- XML 文档内容。
4. XML 声明
XML 文件的第一行通常是:
xml
<?xml version="1.0" encoding="UTF-8"?>
完整格式如下:
xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
4.1 version
表示 XML 版本:
xml
version="1.0"
目前绝大多数项目使用 XML 1.0。
4.2 encoding
表示文档使用的字符编码:
xml
encoding="UTF-8"
推荐统一使用 UTF-8。
需要注意,XML 声明中的编码必须和文件实际保存编码一致。
如果文件实际使用 GBK 保存,却声明为 UTF-8:
xml
<?xml version="1.0" encoding="UTF-8"?>
解析时可能出现乱码或者直接报错。
4.3 standalone
standalone 表示当前 XML 文档是否依赖外部声明:
xml
standalone="yes"
常见取值:
yes:不依赖外部 DTD;no:可能依赖外部 DTD。
在普通业务 XML 中,这个属性可以省略。
5. XML 元素
XML 元素由开始标签、内容和结束标签组成:
xml
<name>张三</name>
其中:
<name>是开始标签;张三是元素内容;</name>是结束标签。
元素中可以继续包含子元素:
xml
<user>
<name>张三</name>
<age>28</age>
</user>
5.1 空元素
没有内容的元素可以写成:
xml
<remark></remark>
也可以简写成:
xml
<remark/>
两种形式在结构上是等价的。
5.2 XML 标签区分大小写
下面两个标签代表不同元素:
xml
<name>张三</name>
<Name>李四</Name>
开始标签和结束标签必须完全一致:
xml
<!-- 正确 -->
<name>张三</name>
下面的写法是错误的:
xml
<!-- 错误 -->
<name>张三</Name>
5.3 XML 必须正确嵌套
正确写法:
xml
<user>
<name>张三</name>
</user>
错误写法:
xml
<user>
<name>张三
</user>
</name>
XML 标签不能交叉嵌套。
5.4 XML 必须有且只有一个根元素
正确:
xml
<users>
<user>
<name>张三</name>
</user>
<user>
<name>李四</name>
</user>
</users>
错误:
xml
<user>
<name>张三</name>
</user>
<user>
<name>李四</name>
</user>
第二种写法存在两个顶级元素,不属于格式正确的 XML 文档。
6. XML 元素命名规则
XML 元素名称需要遵守以下规则:
- 名称区分大小写;
- 名称不能以数字开头;
- 名称不能以标点符号开头;
- 名称不能包含空格;
- 名称不能以任意大小写组合的
xml开头; - 名称中尽量只使用字母、数字、下划线和连字符;
- 冒号通常保留给命名空间使用。
推荐写法:
xml
<user>
<user_id>10001</user_id>
<user-name>张三</user-name>
</user>
不推荐或错误的写法:
xml
<!-- 不能以数字开头 -->
<1user>张三</1user>
<!-- 不能包含空格 -->
<user name>张三</user name>
<!-- xml 前缀具有特殊含义 -->
<xmlUser>张三</xmlUser>
在实际项目中,建议团队统一使用一种命名风格,例如:
xml
<userName>张三</userName>
或者:
xml
<user_name>张三</user_name>
7. XML 属性
元素可以通过属性描述额外信息:
xml
<user id="10001" status="enabled">
<name>张三</name>
</user>
其中:
id是属性名;10001是属性值;status是属性名;enabled是属性值。
7.1 属性值必须使用引号
正确:
xml
<user id="10001"/>
也可以使用单引号:
xml
<user id='10001'/>
错误:
xml
<user id=10001/>
7.2 同一个元素中属性不能重名
正确:
xml
<user id="10001" name="张三"/>
错误:
xml
<user id="10001" id="10002"/>
7.3 属性和子元素应该如何选择
下面两种结构都能表示用户 ID。
使用属性:
xml
<user id="10001">
<name>张三</name>
</user>
使用子元素:
xml
<user>
<id>10001</id>
<name>张三</name>
</user>
通常可以参考以下原则:
- 数据具有复杂结构时,使用子元素;
- 数据可能出现多次时,使用子元素;
- 数据需要扩展时,优先使用子元素;
- 简短的标识、类型、状态,可以使用属性;
- 元素的核心业务数据,通常使用子元素;
- 元素的附加描述信息,可以使用属性。
例如:
xml
<product id="P10001" status="enabled">
<name>机械键盘</name>
<description>支持热插拔和 RGB 灯效</description>
<price currency="CNY">499.00</price>
</product>
8. XML 特殊字符与实体引用
某些字符在 XML 中具有特殊含义,不能直接作为普通文本使用。
| 字符 | 实体引用 | 说明 |
|---|---|---|
< |
< |
小于号 |
> |
> |
大于号 |
& |
& |
与号 |
" |
" |
双引号 |
' |
' |
单引号 |
例如,下面的 XML 是错误的:
xml
<condition>age < 18</condition>
因为解析器会把 < 当成标签开始符号。
正确写法:
xml
<condition>age < 18</condition>
如果文本内容是:
text
Tom & Jerry
XML 中应该写成:
xml
<title>Tom & Jerry</title>
需要特别注意,& 必须正确转义,否则经常会出现类似错误:
text
The entity name must immediately follow the '&'
9. CDATA 区域
如果 XML 中包含大量 <、>、& 等特殊字符,逐个转义会比较麻烦,可以使用 CDATA。
基本格式:
xml
<![CDATA[
这里的内容不会被当成 XML 标签解析
]]>
例如:
xml
<sql>
<![CDATA[
SELECT *
FROM user
WHERE age < 18
AND status <> 'deleted'
]]>
</sql>
保存 JavaScript 代码:
xml
<script>
<![CDATA[
if (age < 18 && status !== "disabled") {
console.log("未成年用户");
}
]]>
</script>
CDATA 只是告诉解析器不要把内部内容当作 XML 标记,它不代表内容被加密或隐藏。
CDATA 内部不能直接出现:
text
]]>
因为这个字符序列表示 CDATA 结束。
如果业务文本中确实包含 ]]>,需要拆分成多个 CDATA 区域,或者对部分字符进行转义。
10. XML 注释
XML 注释格式如下:
xml
<!-- 这是一段注释 -->
例如:
xml
<database>
<!-- 数据库连接地址 -->
<url>jdbc:mysql://localhost:3306/demo</url>
<!-- 数据库用户名 -->
<username>root</username>
</database>
XML 注释不能嵌套:
xml
<!--
外层注释
<!-- 内层注释 -->
-->
上面的写法是错误的。
注释内容中也不能包含连续的两个连字符:
text
--
错误示例:
xml
<!-- 用户配置 -- 生产环境 -->
11. 处理指令
处理指令用于向处理 XML 的应用程序传递信息。
基本格式:
xml
<?目标名称 指令内容?>
例如,可以为 XML 指定 XSL 样式表:
xml
<?xml-stylesheet type="text/xsl" href="users.xsl"?>
完整示例:
xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="users.xsl"?>
<users>
<user>
<name>张三</name>
</user>
</users>
XML 声明的写法看起来与处理指令相似,但它在 XML 标准中具有特殊地位。
12. 一个完整的 XML 示例
下面使用订单数据演示 XML 的常见能力:
xml
<?xml version="1.0" encoding="UTF-8"?>
<order id="ORD202608210001" status="paid">
<customer>
<id>10001</id>
<name>张三</name>
<phone>13800000000</phone>
</customer>
<items>
<item sku="KB001">
<name>机械键盘</name>
<price currency="CNY">499.00</price>
<quantity>1</quantity>
</item>
<item sku="MS001">
<name>无线鼠标</name>
<price currency="CNY">199.00</price>
<quantity>2</quantity>
</item>
</items>
<totalAmount currency="CNY">897.00</totalAmount>
<shippingAddress>
<province>广东省</province>
<city>深圳市</city>
<detail>南山区科技园 1 号</detail>
</shippingAddress>
<remark>
<![CDATA[
工作日送货,联系客户前请先发送短信。
]]>
</remark>
</order>
这个示例包含:
- XML 声明;
- 根元素;
- 元素嵌套;
- 属性;
- 重复元素;
- CDATA;
- 业务数据的层次结构。
13. XML 命名空间
13.1 为什么需要命名空间
不同系统可能定义同名标签,但含义完全不同。
例如,一个 XML 同时保存用户信息和商品信息:
xml
<root>
<name>张三</name>
<name>机械键盘</name>
</root>
两个 <name> 标签无法直接体现所属业务。
命名空间可以解决元素名称冲突。
13.2 定义命名空间
使用 xmlns 声明命名空间:
xml
<?xml version="1.0" encoding="UTF-8"?>
<root
xmlns:user="https://example.com/xml/user"
xmlns:product="https://example.com/xml/product">
<user:name>张三</user:name>
<product:name>机械键盘</product:name>
</root>
这里:
user是命名空间前缀;product是命名空间前缀;https://example.com/xml/user是命名空间 URI;https://example.com/xml/product是另一个命名空间 URI。
命名空间 URI 的主要作用是提供唯一标识,它不一定是一个可以在浏览器中打开的网页地址。
13.3 默认命名空间
可以定义默认命名空间:
xml
<users xmlns="https://example.com/xml/user">
<user>
<name>张三</name>
</user>
</users>
在这个范围内,没有前缀的元素属于默认命名空间。
13.4 默认命名空间不自动作用于普通属性
例如:
xml
<user
xmlns="https://example.com/xml/user"
id="10001"/>
这里:
<user>属于默认命名空间;id属性不属于默认命名空间。
如果属性也需要命名空间,必须显式添加前缀:
xml
<user
xmlns="https://example.com/xml/user"
xmlns:meta="https://example.com/xml/meta"
meta:id="10001"/>
13.5 Java 解析命名空间时的注意事项
使用 DOM 解析时,需要开启命名空间支持:
java
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
读取元素时,建议使用命名空间 URI 和本地名称:
java
NodeList nodes = document.getElementsByTagNameNS(
"https://example.com/xml/user",
"name"
);
不要把命名空间前缀当成永久不变的标识,因为下面两段 XML 的语义可以相同:
xml
<u:name xmlns:u="https://example.com/xml/user">张三</u:name>
xml
<user:name xmlns:user="https://example.com/xml/user">张三</user:name>
真正用于区分命名空间的是 URI,而不是 u 或 user 这样的前缀。
14. 格式正确与文档有效
XML 文档通常涉及两个概念:
14.1 格式正确
只要符合 XML 的基本语法规则,就是格式正确的 XML,例如:
- 有且只有一个根元素;
- 标签正确闭合;
- 标签正确嵌套;
- 属性值使用引号;
- 特殊字符正确转义。
示例:
xml
<user>
<name>张三</name>
</user>
14.2 文档有效
文档有效不仅要求格式正确,还要求符合指定的 DTD 或 XSD 结构。
假设 XSD 规定用户必须包含 id 和 name:
xml
<user>
<name>张三</name>
</user>
这份 XML 虽然格式正确,但因为缺少 id,所以不符合 XSD,也就是无效文档。
15. DTD 文档类型定义
DTD 是一种较早的 XML 结构约束方式。
15.1 内部 DTD
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [
<!ELEMENT user (id, name, email?)>
<!ELEMENT id (#PCDATA)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT email (#PCDATA)>
]>
<user>
<id>10001</id>
<name>张三</name>
<email>zhangsan@example.com</email>
</user>
其中:
text
(id, name, email?)
表示:
id必须出现;name必须出现;email可以出现零次或一次;- 元素顺序必须是
id、name、email。
常见数量符号:
| 符号 | 含义 |
|---|---|
| 无符号 | 必须出现一次 |
? |
出现零次或一次 |
* |
出现零次或多次 |
+ |
出现一次或多次 |
15.2 外部 DTD
XML 文件:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user SYSTEM "user.dtd">
<user>
<id>10001</id>
<name>张三</name>
</user>
user.dtd:
dtd
<!ELEMENT user (id, name)>
<!ELEMENT id (#PCDATA)>
<!ELEMENT name (#PCDATA)>
15.3 DTD 的局限
DTD 存在一些明显限制:
- 数据类型支持较弱;
- DTD 自身不是 XML 语法;
- 命名空间支持有限;
- 难以描述复杂业务约束;
- 外部实体可能带来 XXE 安全风险。
现代业务系统如果需要严格校验,通常更倾向于使用 XSD。
16. XSD:XML Schema Definition
XSD 使用 XML 语法描述 XML 文档结构,功能比 DTD 更强。
它可以定义:
- 元素和属性;
- 字符串、整数、日期、小数等数据类型;
- 元素出现次数;
- 默认值和固定值;
- 字符串长度;
- 数值范围;
- 枚举值;
- 正则表达式;
- 复杂嵌套结构。
16.1 XML 文件
创建 user.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<user
xmlns="https://example.com/xml/user"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://example.com/xml/user
user.xsd">
<id>10001</id>
<name>张三</name>
<age>28</age>
<status>enabled</status>
<email>zhangsan@example.com</email>
</user>
16.2 XSD 文件
创建 user.xsd:
xml
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="https://example.com/xml/user"
xmlns="https://example.com/xml/user"
elementFormDefault="qualified">
<xs:element name="user">
<xs:complexType>
<xs:sequence>
<xs:element name="id" type="xs:long"/>
<xs:element name="name">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
<xs:maxLength value="50"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="age" minOccurs="0">
<xs:simpleType>
<xs:restriction base="xs:int">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="150"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="status">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="enabled"/>
<xs:enumeration value="disabled"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element
name="email"
type="xs:string"
minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
16.3 常见 XSD 数据类型
| 类型 | 说明 |
|---|---|
xs:string |
字符串 |
xs:boolean |
布尔值 |
xs:byte |
字节整数 |
xs:int |
整数 |
xs:long |
长整数 |
xs:decimal |
十进制数 |
xs:float |
单精度浮点数 |
xs:double |
双精度浮点数 |
xs:date |
日期 |
xs:dateTime |
日期时间 |
xs:time |
时间 |
xs:anyURI |
URI |
涉及金额时,通常优先使用 xs:decimal,而不是 xs:float 或 xs:double。
16.4 元素出现次数
xml
<xs:element
name="item"
minOccurs="1"
maxOccurs="unbounded"/>
表示 item:
- 至少出现一次;
- 最多可以出现任意多次。
16.5 sequence、choice 和 all
xs:sequence 表示元素必须按照指定顺序出现:
xml
<xs:sequence>
<xs:element name="id" type="xs:long"/>
<xs:element name="name" type="xs:string"/>
</xs:sequence>
xs:choice 表示多个元素中选择一个:
xml
<xs:choice>
<xs:element name="phone" type="xs:string"/>
<xs:element name="email" type="xs:string"/>
</xs:choice>
xs:all 表示子元素顺序可以不同,但每个元素通常只能出现零次或一次:
xml
<xs:all>
<xs:element name="id" type="xs:long"/>
<xs:element name="name" type="xs:string"/>
</xs:all>
17. XPath 查询 XML
XPath 是一种在 XML 文档中定位元素和属性的查询语言。
假设存在下面的 XML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book id="B001" category="programming">
<title>Java 核心技术</title>
<price>129.00</price>
</book>
<book id="B002" category="database">
<title>高性能 MySQL</title>
<price>99.00</price>
</book>
<book id="B003" category="programming">
<title>Spring 实战</title>
<price>89.00</price>
</book>
</bookstore>
17.1 常见 XPath 表达式
选择根节点下的所有 book:
xpath
/bookstore/book
选择文档中任意位置的 book:
xpath
//book
选择第一个 book:
xpath
/bookstore/book[1]
根据属性筛选:
xpath
//book[@category='programming']
根据 ID 查询:
xpath
//book[@id='B001']
选择所有书名:
xpath
//book/title
选择书名的文本:
xpath
//book/title/text()
查询价格大于 100 的书:
xpath
//book[price > 100]
选择所有 book 的 id 属性:
xpath
//book/@id
组合多个条件:
xpath
//book[@category='programming' and price > 100]
17.2 常见 XPath 符号
| 表达式 | 说明 |
|---|---|
/ |
从当前节点选择直接子节点 |
// |
从任意后代节点中选择 |
. |
当前节点 |
.. |
父节点 |
@ |
选择属性 |
* |
匹配任意元素 |
[条件] |
添加筛选条件 |
text() |
选择文本节点 |
17.3 命名空间对 XPath 的影响
假设 XML 使用默认命名空间:
xml
<users xmlns="https://example.com/xml/user">
<user>
<name>张三</name>
</user>
</users>
下面的 XPath 可能查询不到数据:
xpath
/users/user/name
原因是 XPath 中没有命名空间前缀的名称通常表示"不属于命名空间的元素"。
在 Java 中可以为命名空间绑定前缀,然后查询:
xpath
/u:users/u:user/u:name
这里的 u 是 XPath 上下文中自行绑定的前缀,不要求和原 XML 使用相同前缀,只需要绑定到相同的命名空间 URI。
18. XSLT 转换 XML
XSLT 是一种用于转换 XML 文档的语言。
它可以将 XML 转换为:
- HTML;
- 另一种 XML;
- 普通文本;
- CSV 等文本格式。
假设存在 users.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user>
<id>10001</id>
<name>张三</name>
</user>
<user>
<id>10002</id>
<name>李四</name>
</user>
</users>
创建 users.xsl:
xml
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8"/>
<xsl:template match="/">
<html>
<head>
<meta charset="UTF-8"/>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<table border="1">
<tr>
<th>用户 ID</th>
<th>用户名</th>
</tr>
<xsl:for-each select="users/user">
<tr>
<td>
<xsl:value-of select="id"/>
</td>
<td>
<xsl:value-of select="name"/>
</td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
转换后会得到一张 HTML 用户表格。
XSLT 在传统企业系统、报表系统、消息格式转换和文档发布系统中仍然比较常见。
19. XML 常见解析方式
程序解析 XML 时,常见方式包括:
- DOM;
- SAX;
- StAX;
- 对象映射。
19.1 DOM
DOM 会把整个 XML 文档读取到内存中,并构建一棵节点树。
优点:
- 使用简单;
- 可以随机访问任意节点;
- 可以修改、增加和删除节点;
- 适合结构复杂但文件较小的 XML。
缺点:
- 需要将整个文档加载到内存;
- 大文件可能占用大量内存;
- 首次解析完成前不能处理数据。
19.2 SAX
SAX 是基于事件的解析方式。
解析器从头到尾读取 XML,并触发事件:
- 开始读取文档;
- 遇到开始标签;
- 读取文本;
- 遇到结束标签;
- 文档读取完成。
优点:
- 内存占用低;
- 适合读取大型 XML;
- 适合顺序处理。
缺点:
- 不能方便地向前回退;
- 状态管理相对复杂;
- 不适合频繁随机访问节点。
19.3 StAX
StAX 是流式拉取解析方式。
和 SAX 相比:
- SAX 由解析器主动调用回调方法;
- StAX 由应用程序主动向解析器获取下一个事件。
优点:
- 内存占用低;
- 流程比 SAX 更容易控制;
- 适合大型 XML;
- 可以按需停止解析。
19.4 对象映射
对象映射可以把 XML 直接转换成 Java 对象。
例如:
xml
<user>
<id>10001</id>
<name>张三</name>
</user>
可以映射成:
java
public class User {
private Long id;
private String name;
}
对象映射适用于结构稳定、业务模型明确的 XML。
需要注意,从较新的 JDK 开始,JAXB 通常不再作为 JDK 内置模块直接提供,项目可能需要单独添加 Jakarta XML Binding 相关依赖。
19.5 解析方式对比
| 解析方式 | 内存占用 | 随机访问 | 修改文档 | 适合场景 |
|---|---|---|---|---|
| DOM | 高 | 支持 | 支持 | 小型 XML、配置文件 |
| SAX | 低 | 不支持 | 不方便 | 超大型 XML、顺序处理 |
| StAX | 低 | 不支持 | 不方便 | 大型 XML、可控流式处理 |
| 对象映射 | 中等 | 通过对象访问 | 支持对象修改 | 结构稳定的业务 XML |
20. Java 使用 DOM 解析 XML
20.1 示例 XML
创建 users.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<users>
<user id="10001">
<name>张三</name>
<age>28</age>
</user>
<user id="10002">
<name>李四</name>
<age>30</age>
</user>
</users>
20.2 Java 解析代码
java
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XmlDomExample {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
// 如果 XML 使用命名空间,需要开启
factory.setNamespaceAware(true);
// 安全配置:禁止 DOCTYPE 和外部实体
factory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl",
true
);
factory.setFeature(
"http://xml.org/sax/features/external-general-entities",
false
);
factory.setFeature(
"http://xml.org/sax/features/external-parameter-entities",
false
);
factory.setFeature(
"http://apache.org/xml/features/nonvalidating/load-external-dtd",
false
);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
// 禁止访问外部 DTD 和外部 Schema
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new File("users.xml"));
document.getDocumentElement().normalize();
NodeList userNodes = document.getElementsByTagName("user");
for (int i = 0; i < userNodes.getLength(); i++) {
Node node = userNodes.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element userElement = (Element) node;
String id = userElement.getAttribute("id");
String name = getChildText(userElement, "name");
String age = getChildText(userElement, "age");
System.out.println(
"id=" + id
+ ", name=" + name
+ ", age=" + age
);
}
}
private static String getChildText(
Element parent,
String tagName
) {
NodeList nodes = parent.getElementsByTagName(tagName);
if (nodes.getLength() == 0) {
return null;
}
return nodes.item(0).getTextContent().trim();
}
}
输出结果:
text
id=10001, name=张三, age=28
id=10002, name=李四, age=30
这里需要注意,getElementsByTagName() 会查询所有后代节点,而不只是直接子节点。
如果 XML 结构复杂,建议明确遍历子节点,避免错误读取更深层级中的同名元素。
21. Java 使用 XPath 查询 XML
java
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.File;
public class XmlXPathExample {
public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
factory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl",
true
);
factory.setFeature(
"http://xml.org/sax/features/external-general-entities",
false
);
factory.setFeature(
"http://xml.org/sax/features/external-parameter-entities",
false
);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
Document document = factory
.newDocumentBuilder()
.parse(new File("users.xml"));
XPath xpath = XPathFactory.newInstance().newXPath();
String expression = "/users/user[@id='10001']/name";
NodeList nodes = (NodeList) xpath.evaluate(
expression,
document,
XPathConstants.NODESET
);
for (int i = 0; i < nodes.getLength(); i++) {
System.out.println(nodes.item(i).getTextContent().trim());
}
}
}
输出:
text
张三
不要直接把不可信的用户输入拼接进 XPath:
java
String expression =
"/users/user[@id='" + userInput + "']/name";
这种写法可能产生 XPath 注入风险。
更安全的方式包括:
- 对输入格式进行严格白名单校验;
- 限制输入只能是数字、UUID 等预期格式;
- 使用
XPathVariableResolver绑定变量; - 不允许用户直接控制完整 XPath 表达式。
22. Java 使用 StAX 解析大型 XML
当 XML 文件很大时,可以使用 StAX 流式解析。
java
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
import java.io.FileInputStream;
import java.io.InputStream;
public class XmlStaxExample {
public static void main(String[] args) throws Exception {
XMLInputFactory factory = XMLInputFactory.newFactory();
// 禁止 DTD 和外部实体
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
factory.setProperty(
XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
false
);
try (InputStream input =
new FileInputStream("users.xml")) {
XMLStreamReader reader =
factory.createXMLStreamReader(input, "UTF-8");
String currentElement = null;
while (reader.hasNext()) {
int event = reader.next();
if (event == XMLStreamConstants.START_ELEMENT) {
currentElement = reader.getLocalName();
if ("user".equals(currentElement)) {
String id =
reader.getAttributeValue(null, "id");
System.out.println("用户 ID:" + id);
}
}
if (event == XMLStreamConstants.CHARACTERS) {
String text = reader.getText().trim();
if (!text.isEmpty()
&& ("name".equals(currentElement)
|| "age".equals(currentElement))) {
System.out.println(
currentElement + ":" + text
);
}
}
if (event == XMLStreamConstants.END_ELEMENT) {
currentElement = null;
}
}
reader.close();
}
}
}
实际项目中,文本可能被拆成多个 CHARACTERS 事件。
因此,复杂场景不要假设一个元素的文本只会触发一次字符事件。更稳妥的做法是使用缓冲区累计文本,并在结束标签出现时处理。
23. Java 使用 XSD 校验 XML
java
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.File;
import java.io.IOException;
public class XmlValidationExample {
public static void main(String[] args) throws Exception {
validate(
new File("user.xml"),
new File("user.xsd")
);
}
public static void validate(
File xmlFile,
File xsdFile
) throws SAXException, IOException {
SchemaFactory schemaFactory =
SchemaFactory.newInstance(
XMLConstants.W3C_XML_SCHEMA_NS_URI
);
// 禁止校验过程中访问外部资源
schemaFactory.setProperty(
XMLConstants.ACCESS_EXTERNAL_DTD,
""
);
schemaFactory.setProperty(
XMLConstants.ACCESS_EXTERNAL_SCHEMA,
""
);
Schema schema = schemaFactory.newSchema(xsdFile);
Validator validator = schema.newValidator();
validator.setProperty(
XMLConstants.ACCESS_EXTERNAL_DTD,
""
);
validator.setProperty(
XMLConstants.ACCESS_EXTERNAL_SCHEMA,
""
);
validator.validate(new StreamSource(xmlFile));
System.out.println("XML 校验通过");
}
}
如果 XML 不符合 XSD,会抛出 SAXException。
生产环境中建议输出:
- 文件或请求标识;
- 错误行号;
- 错误列号;
- 校验失败原因;
- 对应业务字段。
但不要在日志中直接输出包含密码、身份证、银行卡或令牌的完整 XML。
24. 生成 XML 时应避免直接拼接字符串
下面这种写法存在风险:
java
String xml =
"<user>"
+ "<name>" + userName + "</name>"
+ "</user>";
如果 userName 的内容是:
text
Tom & Jerry
最终 XML 会变成:
xml
<user>
<name>Tom & Jerry</name>
</user>
由于 & 没有转义,这不是格式正确的 XML。
如果用户输入包含:
text
</name><admin>true</admin><name>
还可能破坏原有结构。
因此,生成 XML 时应该:
- 使用 DOM、StAX、JAXB 等标准 API;
- 让 XML 库自动转义特殊字符;
- 不要直接拼接不可信输入;
- 明确设置输出编码;
- 必要时使用 XSD 校验生成结果。
25. XML 安全:XXE 外部实体攻击
25.1 什么是 XXE
XXE 的全称是 XML External Entity,也就是 XML 外部实体攻击。
如果解析器允许加载外部实体,攻击者可能构造恶意 XML,诱导服务器读取本地文件或者访问内部网络。
示意性恶意 XML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<user>
<name>&file;</name>
</user>
如果服务器错误地允许解析外部实体,&file; 可能被替换成本地文件内容。
在 Windows 环境中,攻击者也可能尝试读取本地敏感文件,或者通过 HTTP 请求访问内网服务。
25.2 XXE 可能造成什么影响
- 读取服务器本地文件;
- 泄露配置文件和密钥;
- 探测内网服务;
- 发起服务端请求伪造;
- 造成拒绝服务;
- 消耗系统资源;
- 泄露云环境元数据。
25.3 防御原则
解析不可信 XML 时,应默认:
- 禁止
DOCTYPE; - 禁止外部通用实体;
- 禁止外部参数实体;
- 禁止加载外部 DTD;
- 禁止 XInclude;
- 禁止访问外部 Schema;
- 开启安全处理限制;
- 限制 XML 文件大小;
- 限制元素深度和数量;
- 限制单个文本节点长度;
- 设置请求超时;
- 使用受维护的解析器版本。
仅设置一个安全参数通常不够,应组合配置。
26. XML 实体膨胀攻击
除了 XXE,还需要注意实体膨胀攻击,也经常被称为"Billion Laughs"。
攻击者通过多层实体递归引用,让一个很小的 XML 在解析后膨胀成非常大的内容,从而消耗大量内存和 CPU。
防御方式包括:
- 禁止 DTD;
- 禁止实体扩展;
- 开启安全处理;
- 限制实体数量;
- 限制 XML 大小;
- 限制解析时间;
- 对上传文件设置大小上限。
27. XML 与 JSON 的区别
XML:
xml
<user>
<id>10001</id>
<name>张三</name>
<roles>
<role>admin</role>
<role>editor</role>
</roles>
</user>
JSON:
json
{
"id": 10001,
"name": "张三",
"roles": [
"admin",
"editor"
]
}
主要区别如下:
| 对比项 | XML | JSON |
|---|---|---|
| 可读性 | 较好,但标签较多 | 通常更简洁 |
| 数据体积 | 通常较大 | 通常较小 |
| 数组表达 | 通过重复元素表达 | 原生支持数组 |
| 数据类型 | 文本为主,可通过 XSD 定义 | 原生支持字符串、数字、布尔值等 |
| 注释 | 支持 | 标准 JSON 不支持 |
| 命名空间 | 原生支持 | 不支持 |
| 混合内容 | 支持较好 | 不擅长 |
| 结构校验 | DTD、XSD | JSON Schema |
| 浏览器支持 | 支持解析 | 前端使用更加普遍 |
| 典型场景 | 配置、文档、SOAP、行业标准 | REST API、前后端数据交换 |
27.1 什么时候选择 XML
适合使用 XML 的场景:
- 已有行业标准要求使用 XML;
- 对方系统只支持 XML;
- 使用 SOAP Web Service;
- 需要 XML 命名空间;
- 需要严格的 XSD 校验;
- 需要表达文档和混合内容;
- 需要使用 XPath 或 XSLT;
- 需要兼容已有企业系统。
27.2 什么时候选择 JSON
适合使用 JSON 的场景:
- RESTful API;
- Web 前后端数据交互;
- 移动端接口;
- 对传输体积比较敏感;
- 数据结构主要是对象和数组;
- 不需要 XML 命名空间和混合内容。
XML 和 JSON 并不是谁完全取代谁,而是适合不同场景。
28. XML 在实际项目中的应用
28.1 Maven 配置
Java Maven 项目的 pom.xml:
xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>xml-demo</artifactId>
<version>1.0.0</version>
</project>
28.2 Spring 配置
早期 Spring 项目大量使用 XML 配置 Bean:
xml
<bean
id="userService"
class="com.example.service.UserService"/>
28.3 MyBatis 映射文件
xml
<select
id="findById"
parameterType="long"
resultType="com.example.User">
SELECT id, name, email
FROM user
WHERE id = #{id}
</select>
MyBatis XML 中经常需要处理 <、> 等符号。
例如:
xml
<select id="findMinorUsers" resultType="com.example.User">
SELECT id, name, age
FROM user
WHERE age < 18
</select>
也可以使用 CDATA:
xml
<select id="findMinorUsers" resultType="com.example.User">
<![CDATA[
SELECT id, name, age
FROM user
WHERE age < 18
]]>
</select>
28.4 Android 布局
xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello XML"/>
</LinearLayout>
28.5 SOAP 消息
xml
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<getUser>
<id>10001</id>
</getUser>
</soap:Body>
</soap:Envelope>
28.6 SVG 图形
SVG 本身也是一种 XML:
xml
<svg
xmlns="http://www.w3.org/2000/svg"
width="200"
height="100">
<rect
x="10"
y="10"
width="180"
height="80"
fill="#409EFF"/>
</svg>
29. 常见 XML 错误及解决方法
29.1 存在多个根元素
错误:
xml
<user>张三</user>
<user>李四</user>
解决:
xml
<users>
<user>张三</user>
<user>李四</user>
</users>
29.2 标签没有闭合
错误:
xml
<user>
<name>张三</name>
解决:
xml
<user>
<name>张三</name>
</user>
29.3 标签交叉嵌套
错误:
xml
<a>
<b>内容
</a>
</b>
解决:
xml
<a>
<b>内容</b>
</a>
29.4 特殊字符没有转义
错误:
xml
<condition>age < 18 && status = 1</condition>
解决:
xml
<condition>
age < 18 && status = 1
</condition>
或者:
xml
<condition>
<![CDATA[
age < 18 && status = 1
]]>
</condition>
29.5 XML 声明编码与文件编码不一致
声明:
xml
<?xml version="1.0" encoding="UTF-8"?>
但文件实际使用 GBK 保存,可能导致乱码或解析失败。
解决方法是统一:
- 文件实际保存编码;
- XML 声明编码;
- HTTP
Content-Type中的编码; - 程序读取时使用的编码;
- 数据库和日志输出编码。
29.6 默认命名空间导致 XPath 查不到数据
XML:
xml
<users xmlns="https://example.com/user">
<user>
<name>张三</name>
</user>
</users>
错误认知:
xpath
/users/user/name
可能查不到结果。
解决方法是为 XPath 配置命名空间前缀,再使用:
xpath
/u:users/u:user/u:name
29.7 XML 中出现不可见非法字符
某些控制字符不能直接出现在 XML 1.0 文档中。
常见来源包括:
- 从二进制文件错误转换;
- 从旧系统复制数据;
- 数据库字段包含控制字符;
- 日志内容未经处理直接写入 XML。
遇到这类问题,应找到原始字节和具体字符位置,不要只做全局替换,否则可能破坏正常业务数据。
29.8 Byte Order Mark 问题
部分 UTF-8 文件可能包含 BOM。
许多现代 XML 解析器能够处理 UTF-8 BOM,但某些旧系统、签名校验流程或错误实现可能出现兼容问题。
如果 XML 需要参与摘要或数字签名,必须明确:
- 是否包含 BOM;
- 是否进行换行转换;
- 是否进行字符编码转换;
- 是否进行 XML 规范化;
- 签名针对原始字节还是规范化结果。
30. XML 设计最佳实践
30.1 使用 UTF-8
推荐:
xml
<?xml version="1.0" encoding="UTF-8"?>
同时确保文件实际以 UTF-8 保存。
30.2 保持标签含义明确
推荐:
xml
<creationTime>2026-08-21T10:30:00+08:00</creationTime>
不推荐:
xml
<ct>2026-08-21 10:30:00</ct>
除非 ct 是行业标准中的固定名称。
30.3 不要依赖元素顺序表达隐藏语义
下面的结构不够清晰:
xml
<values>
<value>张三</value>
<value>28</value>
</values>
更推荐:
xml
<user>
<name>张三</name>
<age>28</age>
</user>
30.4 明确日期时间格式
推荐使用 ISO 8601 格式:
xml
<createdAt>2026-08-21T10:30:00+08:00</createdAt>
不要使用含义不明确的日期:
xml
<createdAt>08/09/10</createdAt>
它可能被理解为多个不同日期。
30.5 金额同时包含币种
xml
<amount currency="CNY">100.00</amount>
程序读取时使用高精度十进制类型,例如 Java 的 BigDecimal,不要使用 double 直接进行金额计算。
30.6 明确空值语义
下面几种结构的含义可能不同:
xml
<!-- 元素不存在 -->
<user/>
xml
<!-- 元素存在,但内容为空 -->
<user>
<name/>
</user>
xml
<!-- 使用 xsi:nil 表示空值 -->
<user xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<name xsi:nil="true"/>
</user>
接口设计时应明确:
- 元素缺失表示什么;
- 空字符串表示什么;
xsi:nil="true"表示什么;- 是否允许元素为空。
30.7 为接口增加版本
可以通过属性表达:
xml
<request version="1.0">
<userId>10001</userId>
</request>
也可以通过命名空间表达:
xml
<request xmlns="https://example.com/api/user/v1">
<userId>10001</userId>
</request>
版本升级时应尽量保持向后兼容。
30.8 使用 XSD 作为接口契约
对外部系统交换 XML 时,建议提供:
- XML 字段说明;
- XSD 文件;
- 完整请求示例;
- 完整响应示例;
- 字段是否必填;
- 字段长度限制;
- 枚举值;
- 日期和金额格式;
- 错误响应结构;
- 命名空间说明;
- 版本兼容策略。
30.9 不要在日志中记录完整敏感 XML
日志输出前需要对敏感字段脱敏,例如:
- 密码;
- Token;
- 身份证号;
- 银行卡号;
- 手机号;
- 个人地址;
- API 密钥。
可以记录:
text
traceId=abc123, orderId=ORD001, xmlValidation=failed, line=18, column=9
避免直接输出整个请求正文。
30.10 永远安全解析不可信 XML
来自以下来源的 XML 都应该视为不可信:
- HTTP 请求;
- 文件上传;
- 消息队列;
- 邮件附件;
- 第三方接口;
- 用户输入;
- 外部存储;
- 数据库中历史保存的 XML。
即使 XML 来自合作方,也应禁用外部实体并设置资源限制。
31. XML 格式化、压缩与规范化
31.1 格式化
便于阅读的 XML:
xml
<user>
<id>10001</id>
<name>张三</name>
</user>
31.2 压缩
去掉不必要空白:
xml
<user><id>10001</id><name>张三</name></user>
两者在很多业务场景中的结构相同,但文本字节并不相同。
31.3 空白字符可能是业务数据
例如:
xml
<message>Hello World</message>
和:
xml
<message>Hello World</message>
文本内容并不一定相同。
不要在不了解业务语义的情况下随意删除文本节点中的空格和换行。
31.4 XML 规范化
同一份 XML 可以有不同的文本写法,例如:
xml
<user id="10001" status="enabled"/>
xml
<user status="enabled" id="10001"></user>
它们在结构上可以等价,但原始字节不同。
在 XML 数字签名、摘要计算和安全协议中,通常需要进行 XML 规范化,也就是 Canonical XML。
规范化可以统一:
- 属性顺序;
- 命名空间声明;
- 空元素写法;
- 换行形式;
- 部分空白处理。
因此,对 XML 做签名时,不应简单地对"格式化后的字符串"进行随意处理,而应遵守具体协议要求的规范化算法。
32. XML 接口设计示例
32.1 请求
xml
<?xml version="1.0" encoding="UTF-8"?>
<createOrderRequest
xmlns="https://example.com/api/order/v1"
requestId="REQ202608210001">
<customerId>10001</customerId>
<items>
<item>
<sku>KB001</sku>
<quantity>1</quantity>
</item>
<item>
<sku>MS001</sku>
<quantity>2</quantity>
</item>
</items>
<shippingAddress>
<province>广东省</province>
<city>深圳市</city>
<detail>南山区科技园 1 号</detail>
</shippingAddress>
</createOrderRequest>
32.2 成功响应
xml
<?xml version="1.0" encoding="UTF-8"?>
<createOrderResponse
xmlns="https://example.com/api/order/v1"
requestId="REQ202608210001">
<success>true</success>
<orderId>ORD202608210001</orderId>
<status>created</status>
<createdAt>2026-08-21T10:30:00+08:00</createdAt>
</createOrderResponse>
32.3 失败响应
xml
<?xml version="1.0" encoding="UTF-8"?>
<errorResponse
xmlns="https://example.com/api/order/v1"
requestId="REQ202608210001">
<success>false</success>
<error>
<code>INVALID_QUANTITY</code>
<message>商品数量必须大于 0</message>
<field>items.item.quantity</field>
</error>
</errorResponse>
一个设计良好的 XML 接口应该能够通过 requestId 或 traceId 串联完整请求链路。
33. XML 常见面试题
33.1 XML 和 HTML 有什么区别
HTML:
- 标签通常是预定义的;
- 主要负责页面展示;
- 浏览器对部分错误具有容错能力;
- 关注内容如何呈现。
XML:
- 标签可以自定义;
- 主要负责描述和传输数据;
- 语法要求严格;
- 关注数据是什么以及数据之间的关系。
33.2 XML 是否区分大小写
区分。
<user> 和 <User> 是两个不同元素。
33.3 XML 可以有多个根元素吗
不可以。一份格式正确的 XML 文档必须有且只有一个根元素。
33.4 CDATA 有什么作用
CDATA 用于保存不希望被 XML 解析器当作标记处理的文本,适合包含大量 <、>、& 的 SQL、脚本或文本片段。
33.5 DOM 和 SAX 有什么区别
DOM 将整个 XML 加载到内存,支持随机访问和修改。
SAX 按顺序流式读取,内存占用较低,但不方便随机访问和回退。
33.6 什么是 XML 命名空间
命名空间通过 URI 区分来自不同业务或标准的同名元素,避免标签名称冲突。
33.7 什么是 XXE
XXE 是由于 XML 解析器错误地允许外部实体而产生的安全漏洞,可能导致本地文件读取、内网访问和拒绝服务。
33.8 DTD 和 XSD 有什么区别
DTD:
- 语法不是标准 XML;
- 数据类型支持较弱;
- 命名空间支持有限。
XSD:
- 自身使用 XML 语法;
- 支持丰富的数据类型;
- 支持命名空间;
- 可以定义长度、范围、枚举和复杂结构。
33.9 XML 中如何表示数组
XML 没有 JSON 那样的数组语法,通常通过重复元素表达:
xml
<users>
<user>张三</user>
<user>李四</user>
</users>
33.10 XML 中的所有数据都是字符串吗
从基础解析角度看,元素文本和属性值首先以字符数据形式出现。
如果使用 XSD、对象映射或业务转换,可以进一步解释为整数、日期、布尔值、小数等类型。
34. 学习 XML 的推荐路线
可以按照下面的顺序学习:
- 掌握 XML 声明、元素和属性;
- 掌握标签闭合、嵌套和根元素规则;
- 掌握特殊字符和 CDATA;
- 理解命名空间;
- 学习 XPath;
- 学习 XSD;
- 掌握 DOM、SAX 和 StAX;
- 学习 Java XML 解析;
- 掌握 XXE 和实体膨胀防御;
- 根据项目需要学习 XSLT、SOAP 或 XML 数字签名。
35. 总结
XML 是一种成熟、严格并且具有强大扩展能力的结构化数据格式。
虽然在现代 Web API 中,JSON 的使用更加普遍,但 XML 仍然广泛存在于:
- Maven 和 MyBatis 配置;
- Spring 传统配置;
- Android 布局;
- SOAP Web Service;
- SVG;
- 金融和支付接口;
- 电商和物流数据交换;
- 政务和医疗行业标准;
- 企业级系统集成;
- XML 数字签名和文档处理。
学习 XML 时,不应该只停留在"会写标签",还需要重点理解:
- 元素和属性的设计原则;
- 命名空间;
- XPath;
- DTD 与 XSD;
- DOM、SAX 和 StAX;
- 字符编码;
- XXE 和实体膨胀攻击;
- XML 规范化和数字签名边界。
对于普通配置文件,可以使用 DOM 简化开发;对于大型 XML,应考虑 SAX 或 StAX;对于外部输入,则必须关闭外部实体、限制资源消耗并进行严格校验。
只要掌握语法、校验、解析和安全这四个核心部分,就能够应对大多数 XML 开发场景。