XML 从入门到实践:语法、命名空间、XPath、XSD 与 Java 安全解析

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>

主要分为两部分:

  1. XML 声明;
  2. 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 中具有特殊含义,不能直接作为普通文本使用。

字符 实体引用 说明
< &lt; 小于号
> &gt; 大于号
& &amp; 与号
" &quot; 双引号
' ' 单引号

例如,下面的 XML 是错误的:

xml 复制代码
<condition>age < 18</condition>

因为解析器会把 < 当成标签开始符号。

正确写法:

xml 复制代码
<condition>age &lt; 18</condition>

如果文本内容是:

text 复制代码
Tom & Jerry

XML 中应该写成:

xml 复制代码
<title>Tom &amp; 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,而不是 uuser 这样的前缀。


14. 格式正确与文档有效

XML 文档通常涉及两个概念:

14.1 格式正确

只要符合 XML 的基本语法规则,就是格式正确的 XML,例如:

  • 有且只有一个根元素;
  • 标签正确闭合;
  • 标签正确嵌套;
  • 属性值使用引号;
  • 特殊字符正确转义。

示例:

xml 复制代码
<user>
    <name>张三</name>
</user>

14.2 文档有效

文档有效不仅要求格式正确,还要求符合指定的 DTD 或 XSD 结构。

假设 XSD 规定用户必须包含 idname

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 可以出现零次或一次;
  • 元素顺序必须是 idnameemail

常见数量符号:

符号 含义
无符号 必须出现一次
? 出现零次或一次
* 出现零次或多次
+ 出现一次或多次

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:floatxs: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]

选择所有 bookid 属性:

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 &lt; 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 &lt; 18 &amp;&amp; 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 接口应该能够通过 requestIdtraceId 串联完整请求链路。


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 的推荐路线

可以按照下面的顺序学习:

  1. 掌握 XML 声明、元素和属性;
  2. 掌握标签闭合、嵌套和根元素规则;
  3. 掌握特殊字符和 CDATA;
  4. 理解命名空间;
  5. 学习 XPath;
  6. 学习 XSD;
  7. 掌握 DOM、SAX 和 StAX;
  8. 学习 Java XML 解析;
  9. 掌握 XXE 和实体膨胀防御;
  10. 根据项目需要学习 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 开发场景。

相关推荐
长谷深风1111 小时前
好的 Tool Schema,不是字段越全越好
java·大数据·人工智能·ai agent·agent工作流·智能体设计·ai产品设计
IT 行者1 小时前
用 HiddenHttpMethodFilter 解决浏览器不支持 PUT/DELETE/PATCH 的问题
java·spring
晚安code1 小时前
Redis 内存淘汰策略实战:什么时候淘汰、怎么选、怎么防缓存击穿
java·redis·缓存
winfredzhang1 小时前
用 Python + wxPython 造一个照片工具箱:PDF / 加密ZIP / MP4 / 归档,以及我在这过程中踩到的 4 个坑
python·pdf·zip·mp4·移动
goyeer1 小时前
Liunx日志管理与journalctl
java·linux·运维·服务器·运维开发·信息化·信息化企业管理
卷无止境1 小时前
FastAPI 后台任务的边界,以及 Celery、Redis 与自建调度系统的选择
后端·python
卷无止境2 小时前
Linux + Docker + FastAPI 工程化实践指南
后端·python·fastapi
tianyu2343 小时前
Java 正则表达式终极指南:从 Pattern 到 Matcher,从双重转义到性能优化,一篇全搞定
java·开发语言·正则表达式·group·find·pattern·matcher
Emily156853598705 小时前
Emerson 1C31129G03 Analog Input Module
java·开发语言·前端·plc·emerson·1c31129g03·input module