Java 中文官方教程 2022 版(四十)

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

课程:XML 的流式 API

原文:docs.oracle.com/javase/tutorial/jaxp/stax/index.html

本课程专注于 XML 的流式 API(StAX),这是一种基于 Java 技术的流式、事件驱动、拉取解析的 API,用于读取和写入 XML 文档。StAX 使您能够创建快速、相对易于编程且具有轻量级内存占用的双向 XML 解析器。

为什么选择 StAX?

原文:docs.oracle.com/javase/tutorial/jaxp/stax/why.html

StAX 项目由 BEA 主导,得到了 Sun Microsystems 的支持,JSR 173 规范于 2004 年 3 月通过了 Java 社区流程的最终批准投票。StAX API 的主要目标是通过公开一个简单的基于迭代器的 API,将“解析控制权交给程序员。这允许程序员请求下一个事件(拉取事件),并允许以过程化方式存储状态。” StAX 的创建是为了解决两种最常见解析 API,SAX 和 DOM,的限制。

流式处理与 DOM

一般来说,处理 XML 信息集有两种编程模型:流式处理文档对象模型(DOM)。

DOM 模型涉及创建代表整个文档树和 XML 文档的完整信息集状态的内存对象。一旦在内存中,DOM 树可以自由导航和任意解析,因此为开发人员提供了最大的灵活性。然而,这种灵活性的代价是潜在的大内存占用和显著的处理器需求,因为整个文档的表示必须作为对象在内存中保持,以便在文档处理期间使用。在处理小型文档时,这可能不是问题,但随着文档大小的增加,内存和处理器需求可能会迅速升高。

流式处理是指一种编程模型,在应用程序运行时串行传输和解析 XML 信息集,通常是实时的,并且通常来自动态来源,其内容事先并不完全知晓。此外,基于流的解析器可以立即开始生成输出,并且信息集元素在使用后可以立即丢弃和进行垃圾回收。虽然提供了较小的内存占用、降低的处理器需求和在某些情况下更高的性能,但流处理的主要折衷是您只能在文档中的一个位置看到信息集状态。您基本上受限于文档的“纸板筒”视图,这意味着您需要在阅读 XML 文档之前知道要进行哪些处理。

在处理 XML 时,流式处理模型特别适用于应用程序具有严格的内存限制,比如在运行 Java 平台微版(Java ME 平台)的手机上,或者当应用程序需要同时处理多个请求时,比如在应用服务器上。实际上,可以说大多数 XML 业务逻辑都可以从流式处理中受益,并且不需要在内存中维护整个 DOM 树。

拉取解析与推送解析

拉取解析是一种编程模型,其中客户端应用程序在需要与 XML 信息集交互时调用 XML 解析库的方法,即客户端只有在明确请求时才会获取(拉取)XML 数据。

推送解析是一种编程模型,其中 XML 解析器在遇到 XML 信息集中的元素时向客户端发送(推送)XML 数据,即使客户端此时还没有准备好使用它。

在处理 XML 流时,拉取解析相比于推送解析提供了几个优势:

  • 在拉取解析中,客户端控制应用程序线程,并且可以在需要时调用解析器的方法。相比之下,在推送处理中,解析器控制应用程序线程,客户端只能接受解析器的调用。

  • 拉取解析库可以比推送库更小,与这些库交互的客户端代码也更简单,即使对于更复杂的文档。

  • 拉取客户端可以使用单个线程同时读取多个文档。

  • StAX 拉取解析器可以过滤 XML 文档,使客户端不需要的元素被忽略,并且可以支持非 XML 数据的 XML 视图。

StAX 使用案例

StAX 规范定义了 API 的许多用例:

  • 数据绑定

    • 反编组 XML 文档

    • 将 XML 文档编组

    • 并行文档处理

    • 无线通信

  • 简单对象访问协议(SOAP)消息处理

    • 解析简单可预测的结构

    • 解析具有前向引用的图形表示

    • 解析 Web 服务描述语言(WSDL)

  • 虚拟数据源

    • 查看存储在数据库中的 XML 数据

    • 查看由 XML 数据绑定创建的 Java 对象中的数据

    • 将 DOM 树作为事件流导航

  • 解析特定的 XML 词汇

  • 管道化 XML 处理

对所有这些用例的完整讨论超出了本课程的范围。请参考 StAX 规范以获取更多信息。

将 StAX 与其他 JAXP API 进行比较

作为 JAXP 家族中的一个 API,StAX 可以与 SAX、TrAX 和 JDOM 等其他 API 进行比较。在后两者中,StAX 不像 TrAX 或 JDOM 那样强大或灵活,但也不需要太多内存或处理器负载才能发挥作用,并且在许多情况下,StAX 可以胜过基于 DOM 的 API。上面概述的相同论点,权衡 DOM 模型与流模型的成本/效益,在这里同样适用。

有鉴于此,最接近的比较可以在 StAX 和 SAX 之间进行,正是在这里 StAX 提供了许多情况下有益的功能;其中一些包括:

  • 使用 StAX 的客户端通常比使用 SAX 的客户端更容易编码。虽然可以说 SAX 解析器稍微更容易编写,但 StAX 解析器的代码可能更小,客户端与解析器交互所需的代码更简单。

  • StAX 是一个双向 API,意味着它既可以读取又可以写入 XML 文档。SAX 只能读取,所以如果你想要写入 XML 文档,就需要另一个 API。

  • SAX 是一个推送 API,而 StAX 是一个拉取 API。上面概述的推送和拉取 API 之间的权衡在这里也适用。

以下表格总结了 StAX、SAX、DOM 和 TrAX 的比较特性。(表格改编自 Jeff Ryan 的文章Does StAX Belong in Your XML Toolbox?)。

XML 解析器 API 特性摘要

特性 StAX SAX DOM TrAX
API 类型 拉取,流式 推送,流式 内存树 XSLT 规则
使用便捷性
XPath 能力
CPU 和内存效率 良好 良好 各异 各异
仅向前
读取 XML
写入 XML
创建,读取,更新,删除

StAX API

原文:docs.oracle.com/javase/tutorial/jaxp/stax/api.html

StAX API 公开了用于 XML 文档的迭代式、基于事件的处理的方法。XML 文档被视为一系列经过过滤的事件,并且信息集状态可以以过程化方式存储。此外,与 SAX 不同,StAX API 是双向的,可以实现对 XML 文档的读取和写入。

StAX API 实际上是两个不同的 API 集:一个光标 API 和一个迭代器 API。这两个 API 集将在本课程的后面更详细地解释,但它们的主要特点如下所述。

光标 API

如其名称所示,StAX 光标 API 表示一个光标,您可以使用它从头到尾遍历 XML 文档。这个光标一次只能指向一件事,并且总是向前移动,从不后退,通常一次移动一个信息集元素。

两个主要的光标接口是XMLStreamReaderXMLStreamWriterXMLStreamReader包括了从 XML 信息模型中检索所有可能信息的访问方法,包括文档编码、元素名称、属性、命名空间、文本节点、起始标记、注释、处理指令、文档边界等等;例如:

public interface XMLStreamReader {
    public int next() throws XMLStreamException;
    public boolean hasNext() throws XMLStreamException;

    public String getText();
    public String getLocalName();
    public String getNamespaceURI();
    // ... other methods not shown
}

您可以在XMLStreamReader上调用诸如getTextgetName之类的方法,以获取当前光标位置的数据。XMLStreamWriter提供了与StartElementEndElement事件类型对应的方法;例如:

public interface XMLStreamWriter {
    public void writeStartElement(String localName) throws XMLStreamException;
    public void writeEndElement() throws XMLStreamException;
    public void writeCharacters(String text) throws XMLStreamException;
    // ... other methods not shown
}

光标 API 与 SAX 在许多方面相似。例如,可以直接访问字符串和字符信息的方法可用,并且可以使用整数索引访问属性和命名空间信息。与 SAX 一样,光标 API 方法将 XML 信息作为字符串返回,这减少了对象分配的需求。

迭代器 API

StAX 迭代器 API 将 XML 文档流表示为一组离散的事件对象。这些事件由应用程序拉取,并由解析器按照它们在源 XML 文档中读取的顺序提供。

基本的迭代器接口称为XMLEvent,并且为事件迭代器 API 表中列出的每种事件类型都有子接口。用于读取迭代器事件的主要解析器接口是XMLEventReader,用于写入迭代器事件的主要接口是XMLEventWriterXMLEventReader接口包含五种方法,其中最重要的是nextEvent,它返回 XML 流中的下一个事件。XMLEventReader实现了java.util.Iterator,这意味着从XMLEventReader返回的内容可以被缓存或传递给可以与标准 Java 迭代器一起工作的程序;例如:

public interface XMLEventReader extends Iterator {
    public XMLEvent nextEvent() throws XMLStreamException;
    public boolean hasNext();
    public XMLEvent peek() throws XMLStreamException;
    // ...
}

类似地,在迭代器 API 的输出端,你有:

public interface XMLEventWriter {
    public void flush() throws XMLStreamException;
    public void close() throws XMLStreamException;
    public void add(XMLEvent e) throws XMLStreamException;
    public void add(Attribute attribute) throws XMLStreamException;
    // ...
}

迭代器事件类型

XMLEvent在事件迭代器 API 中定义的类型

事件类型 描述
StartDocument 报告一组 XML 事件的开始,包括编码、XML 版本和独立属性。
StartElement 报告元素的开始,包括任何属性和命名空间声明;还提供了开始标记的前缀、命名空间 URI 和本地名称的访问。
EndElement 报告元素的结束标记。如果已在相应的 StartElement 上显式设置了命名空间,则在此处可以调用已经超出范围的命名空间。
Characters 对应于 XML CData 部分和 CharacterData 实体。请注意,可忽略的空格和重要的空格也被报告为 Character 事件。
EntityReference 字符实体可以作为独立事件报告,应用程序开发人员可以选择解析或传递未解析的实体。默认情况下,实体会被解析。或者,如果不想将实体报告为事件,则可以替换文本并报告为 Characters
ProcessingInstruction 报告底层处理指令的目标和数据。
Comment 返回注释的文本。
EndDocument 报告一组 XML 事件的结束。
DTD 报告与流相关联的(如果有的话)DTD 的信息,并提供一种返回在 DTD 中找到的自定义对象的方法。
Attribute 属性通常作为 StartElement 事件的一部分报告。然而,有时希望将属性作为独立的 Attribute 事件返回;例如,当命名空间作为 XQueryXPath 表达式的结果返回时。
Namespace 与属性一样,命名空间通常作为 StartElement 的一部分报告,但有时希望将命名空间作为独立的 Namespace 事件报告。

请注意,只有在处理的文档包含 DTD 时,才会创建 DTDEntityDeclarationEntityReferenceNotationDeclarationProcessingInstruction 事件。

事件映射示例

作为事件迭代器 API 如何映射 XML 流的示例,请考虑以下 XML 文档:

<?xml version="1.0"?>
<BookCatalogue >
    <Book>
        <Title>Yogasana Vijnana: the Science of Yoga</Title>
        <ISBN>81-40-34319-4</ISBN>
        <Cost currency="INR">11.50</Cost>
    </Book>
</BookCatalogue>

此文档将被解析为十八个主要和次要事件,如下表所示。请注意,通常从主要事件而不是直接访问,可以访问用大括号({})显示的次要事件。

迭代器 API 事件映射示例

# 元素/属性 事件
1 version="1.0" StartDocument
2 isCData = false data = "\n" IsWhiteSpace = true Characters
3 qname = BookCatalogue:http://www.publishing.org 属性 = null 命名空间 = {BookCatalogue" -> http://www.publishing.org"} StartElement
4 qname = 书 属性 = null 命名空间 = null StartElement
5 qname = 标题 属性 = null 命名空间 = null StartElement
6 isCData = false data = "Yogasana Vijnana: the Science of Yoga\n\t" IsWhiteSpace = false Characters
7 qname = Title namespaces = null EndElement
8 qname = ISBN attributes = null namespaces = null StartElement
9 isCData = false data = "81-40-34319-4\n\t" IsWhiteSpace = false Characters
10 qname = ISBN namespaces = null EndElement
11 qname = Cost attributes = {"currency" -> INR} namespaces = null StartElement
12 isCData = false data = "11.50\n\t" IsWhiteSpace = false Characters
13 qname = Cost namespaces = null EndElement
14 isCData = false data = "\n" IsWhiteSpace = true Characters
15 qname = Book namespaces = null EndElement
16 isCData = false data = "\n" IsWhiteSpace = true Characters
17 qname = BookCatalogue:http://www.publishing.org namespaces = {BookCatalogue" -> http://www.publishing.org"} EndElement
18 EndDocument

在这个例子中有几个重要的事项需要注意:

  • 事件按照文档中遇到相应的 XML 元素的顺序创建,包括元素的嵌套、打开和关闭元素、属性顺序、文档开始和文档结束等。

  • 与正确的 XML 语法一样,所有容器元素都有相应的开始和结束事件;例如,每个 StartElement 都有一个对应的 EndElement,即使是空元素也是如此。

  • Attribute 事件被视为次要事件,并且可以从其对应的 StartElement 事件中访问。

  • Attribute 事件类似,Namespace 事件被视为次要事件,但在事件流中出现两次,并且可以从它们对应的 StartElementEndElement 中分别访问两次。

  • 所有元素都指定了 Character 事件,即使这些元素没有字符数据。同样,Character 事件可以跨事件分割。

  • StAX 解析器维护一个命名空间堆栈,其中保存了当前元素及其祖先元素定义的所有 XML 命名空间信息。通过 javax.xml.namespace.NamespaceContext 接口暴露的命名空间堆栈可以通过命名空间前缀或 URI 访问。

在游标和迭代器 API 之间进行选择

此时合理地问一下,“我应该选择哪个 API?我应该创建 XMLStreamReader 还是 XMLEventReader 的实例?为什么会有两种类型的 API?”

开发目标

StAX 规范的作者针对三种类型的开发者:

  • 图书馆和基础设施开发者:创建应用服务器、JAXM、JAXB、JAX-RPC 等实现;需要高效、低级别的 API,并且具有最小的可扩展性要求。

  • Java ME 开发者:需要小型、简单的拉取解析库,并且具有最小的可扩展性需求。

  • Java 平台企业版(Java EE)和 Java 平台标准版(Java SE)开发人员:需要干净、高效的拉取解析库,同时需要灵活性来读取和写入 XML 流,创建新的事件类型,并扩展 XML 文档元素和属性。

鉴于这些广泛的开发类别,StAX 的作者认为定义两个小型、高效的 API 比过载一个更大、必然更复杂的 API 更有用。

比较游标和迭代器 API

在选择游标和迭代器 API 之间之前,你应该注意一些你可以使用迭代器 API 而不能使用游标 API 的事项:

  • XMLEvent子类创建的对象是不可变的,可以在数组、列表和映射中使用,并且可以在解析器继续处理后传递到你的应用程序中。

  • 你可以创建XMLEvent的子类型,这些子类型可以是全新的信息项,也可以是现有项目的扩展,但具有额外的方法。

  • 你可以以比游标 API 更简单的方式向 XML 事件流中添加和删除事件。

同样,在做出选择时,请记住一些一般性建议:

  • 如果你正在为特别受内存限制的环境编程,比如 Java ME,你可以使用游标 API 创建更小、更高效的代码。

  • 如果性能是你的最高优先级——例如,在创建低级库或基础设施时——游标 API 更有效率。

  • 如果你想创建 XML 处理管道,请使用迭代器 API。

  • 如果你想修改事件流,请使用迭代器 API。

  • 如果你希望你的应用程序能够处理事件流的可插拔处理,请使用迭代器 API。

  • 一般来说,如果你没有明确偏好,建议使用迭代器 API,因为它更灵活、可扩展,从而“未雨绸缪”你的应用程序。

使用 StAX

原文:docs.oracle.com/javase/tutorial/jaxp/stax/using.html

一般来说,StAX 程序员通过使用 XMLInputFactoryXMLOutputFactoryXMLEventFactory 类来创建 XML 流读取器、写入器和事件。通过在工厂上设置属性来进行配置,可以通过在工厂上使用 setProperty 方法将特定于实现的设置传递给底层实现。类似地,可以使用 getProperty 工厂方法查询特定于实现的设置。

下面描述了 XMLInputFactoryXMLOutputFactoryXMLEventFactory 类,然后讨论了资源分配、命名空间和属性管理、错误处理,最后使用游标和迭代器 API 读取和写入流。

StAX 工厂类

StAX 工厂类。XMLInputFactoryXMLOutputFactoryXMLEventFactory,让您定义和配置 XML 流读取器、流写入器和事件类的实现实例。

XMLInputFactory

XMLInputFactory 类允许您配置由工厂创建的 XML 流读取器处理器的实现实例。通过在类上调用 newInstance 方法来创建抽象类 XMLInputFactory 的新实例。然后使用静态方法 XMLInputFactory.newInstance 来创建新的工厂实例。

派生自 JAXP,XMLInputFactory.newInstance 方法通过以下查找过程确定要加载的特定 XMLInputFactory 实现类:

  1. 使用 javax.xml.stream.XMLInputFactory 系统属性。

  2. 使用 Java SE 平台的 Java Runtime Environment (JRE) 目录中的 lib/xml.stream.properties 文件。

  3. 如果可用,使用 Services API 通过查找 JRE 中可用的 JAR 文件中的 META-INF/services/javax.xml.stream.XMLInputFactory 文件确定类名。

  4. 使用平台默认的 XMLInputFactory 实例。

在获取适当的 XMLInputFactory 引用之后,应用程序可以使用工厂来配置和创建流实例。以下表格列出了 XMLInputFactory 支持的属性。详细列表请参阅 StAX 规范。

javax.xml.stream.XMLInputFactory 属性

属性 描述
isValidating 打开实现特定的验证。
isCoalescing (必需) 要求处理器合并相邻的字符数据。
isNamespaceAware 关闭命名空间支持。所有实现必须支持命名空间。对非命名空间感知文档的支持是可选的。
isReplacingEntityReferences (必需) 要求处理器用其替换值替换内部实体引用,并将其报告为字符或描述实体的事件集。
isSupportingExternalEntities (必需) 要求处理器解析外部解析实体。
reporter (必需) 设置并获取XMLReporter接口的实现。
resolver (必需) 设置并获取XMLResolver接口的实现。
allocator (必需) 设置并获取XMLEventAllocator接口的实现。

XMLOutputFactory

通过在类上调用newInstance方法来创建抽象类XMLOutputFactory的新实例。然后使用静态方法XMLOutputFactory.newInstance来创建一个新的工厂实例。用于获取实例的算法与XMLInputFactory相同,但引用javax.xml.stream.XMLOutputFactory系统属性。

XMLOutputFactory 只支持一个属性,即javax.xml.stream.isRepairingNamespaces。此属性是必需的,其目的是创建默认前缀并将其与命名空间 URI 关联起来。有关更多信息,请参阅 StAX 规范。

XMLEventFactory

通过在类上调用newInstance方法来创建抽象类XMLEventFactory的新实例。然后使用静态方法XMLEventFactory.newInstance来创建一个新的工厂实例。此工厂引用javax.xml.stream.XMLEventFactory属性来实例化工厂。用于获取实例的算法与XMLInputFactoryXMLOutputFactory相同,但引用javax.xml.stream.XMLEventFactory系统属性。

XMLEventFactory 没有默认属性。

资源、命名空间和错误

StAX 规范处理资源解析、属性和命名空间,以及错误和异常,如下所述。

资源解析

XMLResolver接口提供了在 XML 处理期间解析资源的方法。应用程序在XMLInputFactory上设置接口,然后该工厂实例创建的所有处理器都设置该接口。

属性和命名空间

属性由 StAX 处理器使用游标接口中的查找方法和字符串以及迭代器接口中的AttributeNamespace事件报告。请注意,命名空间被视为属性,尽管在游标和迭代器 API 中,命名空间与属性分开报告。还要注意,命名空间处理对于 StAX 处理器是可选的。有关命名空间绑定和可选命名空间处理的完整信息,请参阅 StAX 规范。

错误报告和异常处理

所有致命错误都通过javax.xml.stream.XMLStreamException接口报告。所有非致命错误和警告都使用javax.xml.stream.XMLReporter接口报告。

读取 XML 流

正如在本课程前面所描述的,使用 StAX 处理器读取 XML 流的方式——更重要的是,您得到的内容——取决于您是使用 StAX 游标 API 还是事件迭代器 API,这两个部分描述了如何使用这两个 API 读取 XML 流。

使用 XMLStreamReader

StAX 游标 API 中的XMLStreamReader接口只允许您以向前方向读取 XML 流或文档,每次只能读取信息集中的一个项目。以下方法可用于从流中提取数据或跳过不需要的事件:

  • 获取属性的值

  • 读取 XML 内容

  • 确定一个元素是否有内容或为空

  • 获取对属性集合的索引访问

  • 获取对命名空间集合的索引访问

  • 获取当前事件的名称(如果适用)

  • 获取当前事件的内容(如果适用)

XMLStreamReader的实例在任何时候都有一个当前事件,其方法在其上操作。当您在流上创建一个XMLStreamReader实例时,初始当前事件是START_DOCUMENT状态。然后可以使用XMLStreamReader.next方法来跳到流中的下一个事件。

读取属性、属性和命名空间

XMLStreamReader.next方法加载流中下一个事件的属性。然后,您可以通过调用XMLStreamReader.getLocalNameXMLStreamReader.getText方法来访问这些属性。

XMLStreamReader游标位于StartElement事件上时,它读取事件的名称和任何属性,包括命名空间。可以使用索引值访问事件的所有属性,并且还可以通过命名空间 URI 和本地名称查找。但请注意,只有当前StartEvent上声明的命名空间可用;之前声明的命名空间不会被保留,重新声明的命名空间也不会被移除。

XMLStreamReader 方法

XMLStreamReader提供以下方法来检索有关命名空间和属性的信息:

int getAttributeCount();
String getAttributeNamespace(int index);
String getAttributeLocalName(int index);
String getAttributePrefix(int index);
String getAttributeType(int index);
String getAttributeValue(int index);
String getAttributeValue(String namespaceUri, String localName);
boolean isAttributeSpecified(int index);

也可以使用三种额外的方法访问命名空间:

int getNamespaceCount();
String getNamespacePrefix(int index);
String getNamespaceURI(int index);

实例化一个 XMLStreamReader

这个示例取自 StAX 规范,展示了如何实例化一个输入工厂,创建一个读取器,并遍历 XML 流的元素:

XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader( ... );
while(r.hasNext()) {
    r.next();
}

使用 XMLEventReader

StAX 事件迭代器 API 中的XMLEventReader API 提供了将 XML 流中的事件映射到可以自由重用的分配的事件对象的方法,并且 API 本身可以扩展以处理自定义事件。

XMLEventReader提供了四种方法来迭代解析 XML 流:

  • next:返回流中的下一个事件

  • nextEvent:返回下一个类型化的 XMLEvent

  • hasNext:如果流中有更多事件要处理,则返回 true

  • peek:返回事件但不迭代到下一个事件

例如,以下代码片段说明了XMLEventReader方法声明:

package javax.xml.stream;
import java.util.Iterator;

public interface XMLEventReader extends Iterator {
    public Object next();
    public XMLEvent nextEvent() throws XMLStreamException;
    public boolean hasNext();
    public XMLEvent peek() throws XMLStreamException;
    // ...
}

要读取流上的所有事件然后打印它们,您可以使用以下方法:

while(stream.hasNext()) {
    XMLEvent event = stream.nextEvent();
    System.out.print(event);
}

读取属性

您可以从其关联的javax.xml.stream.StartElement中访问属性,如下所示:

public interface StartElement extends XMLEvent {
    public Attribute getAttributeByName(QName name);
    public Iterator getAttributes();
}

您可以使用StartElement接口上的getAttributes方法来使用在该StartElement上声明的所有属性的Iterator

读取命名空间

与读取属性类似,命名空间是通过调用StartElement接口上的getNamespaces方法创建的Iterator来读取的。仅返回当前StartElement的命名空间,并且应用程序可以通过使用StartElement.getNamespaceContext来获取当前命名空间上下文。

写入 XML 流

StAX 是一个双向 API,游标和事件迭代器 API 都有自己的一套接口用于写入 XML 流。与读取流的接口一样,写入器 API 对于游标和事件迭代器之间存在显著差异。以下部分描述了如何使用这些 API 之一来写入 XML 流。

使用 XMLStreamWriter

StAX 游标 API 中的XMLStreamWriter接口允许应用程序写回到 XML 流或创建全新的流。XMLStreamWriter 具有让您执行以下操作的方法:

  • 写入格式良好的 XML

  • 刷新或关闭输出

  • 写入限定名称

请注意,XMLStreamWriter实现不需要对输入执行格式良好性或有效性检查。虽然一些实现可能执行严格的错误检查,但其他可能不会。您实现的规则适用于XMLOutputFactory类中定义的属性。

使用writeCharacters方法转义字符,如&<>"。绑定前缀可以通过传递前缀的实际值,使用setPrefix方法,或设置默认命名空间声明的属性来处理。

以下示例取自 StAX 规范,展示了如何实例化输出工厂,创建写入器并写入 XML 输出:

XMLOutputFactory output = XMLOutputFactory.newInstance();
XMLStreamWriter writer = output.createXMLStreamWriter( ... );

writer.writeStartDocument();
writer.setPrefix("c","http://c");
writer.setDefaultNamespace("http://c");

writer.writeStartElement("http://c","a");
writer.writeAttribute("b","blah");
writer.writeNamespace("c","http://c");
writer.writeDefaultNamespace("http://c");

writer.setPrefix("d","http://c");
writer.writeEmptyElement("http://c","d");
writer.writeAttribute("http://c", "chris","fry");
writer.writeNamespace("d","http://c");
writer.writeCharacters("Jean Arp");
writer.writeEndElement();

writer.flush();

此代码生成以下 XML(新行不是规范性的):

<?xml version=’1.0’ encoding=’utf-8’?>
<a b="blah"  >
<d:d d:chris="fry" />Jean Arp</a>

使用 XMLEventWriter

StAX 事件迭代器 API 中的XMLEventWriter接口允许应用程序写回到 XML 流或创建全新的流。此 API 可以扩展,但主要 API 如下:

public interface XMLEventWriter {
    public void flush() throws XMLStreamException;
    public void close() throws XMLStreamException;
    public void add(XMLEvent e) throws XMLStreamException;
    // ... other methods not shown.
}

XMLEventWriter的实例是由XMLOutputFactory的实例创建的。流事件被迭代地添加,一旦添加到事件写入器实例后,事件就不能被修改。

属性、转义字符、绑定前缀

StAX 实现需要缓冲最后一个StartElement,直到在流中添加或遇到除AttributeNamespace之外的事件。这意味着当您向流中添加AttributeNamespace时,它会附加到当前的StartElement事件。

您可以使用Characters方法转义字符如&<>"

setPrefix(...) 方法可用于显式绑定输出时使用的前缀,而 getPrefix(...) 方法可用于获取当前前缀。请注意,默认情况下,XMLEventWriter 会将命名空间绑定添加到其内部命名空间映射中。前缀在绑定它们的事件对应的 EndElement 后会失效。

Oracle 的流式 XML 解析器实现

原文:docs.oracle.com/javase/tutorial/jaxp/stax/parser.html

应用服务器 9.1 包含 Sun 微系统的 JSR 173(StAX)实现,称为 Sun Java 流式 XML 解析器(简称为流式 XML 解析器)。流式 XML 解析器是一个高速、非验证的、符合 W3C XML 1.0 和 Namespace 1.0 标准的流式 XML 拉取解析器,构建在 Xerces2 代码库之上。

在 Sun 的流式 XML 解析器实现中,Xerces2 的底层,特别是 Scanner 和相关类,已经重新设计为拉取方式。除了底层的更改外,流式 XML 解析器还包括额外的与 StAX 相关的功能和许多性能增强改进。流式 XML 解析器实现在 appserv-ws.jarjavaee.jar 文件中,这两个文件位于 install_dir/lib/ 目录中。

JAXP 参考实现中包含了 StAX 代码示例,位于 INSTALL_DIR/jaxp-version/samples/stax 目录中,展示了 Sun 的流式 XML 解析器实现的工作原理。这些示例在 示例代码 中有描述。

在继续使用示例代码之前,有两个关于流式 XML 解析器的方面需要注意:

  • 报告 CDATA 事件

  • 流式 XML 解析器工厂实现

下面将讨论这些主题。

报告 CDATA 事件

流式 XML 解析器中实现的 javax.xml.stream.XMLStreamReader 不报告 CDATA 事件。如果您有一个需要接收此类事件的应用程序,请配置 XMLInputFactory 来设置以下特定于实现的 report-cdata-event 属性:

XMLInputFactory factory = XMLInptuFactory.newInstance();
factory.setProperty("report-cdata-event", Boolean.TRUE);

流式 XML 解析器工厂实现

大多数应用程序不需要知道工厂实现类名。对于大多数应用程序,只需将 javaee.jarappserv-ws.jar 文件添加到类路径即可,因为这两个 jar 文件在 META-INF/services 目录下提供了各种流式 XML 解析器属性的工厂实现类名,例如 javax.xml.stream.XMLInputFactoryjavax.xml.stream.XMLOutputFactoryjavax.xml.stream.XMLEventFactory,这是应用程序请求工厂实例时查找操作的第三步。有关查找机制的更多信息,请参阅 XMLInputFactory.newInstance 方法的 Javadoc。

但是,在某些情况下,应用程序可能希望了解工厂实现类名并显式设置属性。这些情况可能包括类路径中存在多个 JSR 173 实现,应用程序希望选择其中一个,也许是性能更好的一个,包含了关键的错误修复,或类似情况。

如果一个应用程序设置了SystemProperty,那么这是查找操作的第一步,因此获取工厂实例相对于其他选项来说会更快;例如:

javax.xml.stream.XMLInputFactory -->
  com.sun.xml.stream.ZephyrParserFactory

javax.xml.stream.XMLOutputFactory -->
  com.sun.xml.stream.ZephyrWriterFactor

javax.xml.stream.XMLEventFactory -->
  com.sun.xml.stream.events.ZephyrEventFactory

示例代码

原文:docs.oracle.com/javase/tutorial/jaxp/stax/example.html

本节逐步介绍了 JAXP 参考实现包中包含的示例 StAX 代码。本节中使用的所有示例目录均位于INSTALL_DIR/jaxp-version/samples/stax目录中。

本节涵盖的主题如下:

  • 示例代码组织

  • 示例 XML 文档

  • 游标示例

  • 游标到事件示例

  • 事件示例

  • 过滤器示例

  • 读写示例

  • 写入示例

示例代码组织

INSTALL_DIR/jaxp-version/samples/stax目录包含六个 StAX 示例目录:

  • 游标示例cursor目录包含CursorParse.java,演示如何使用XMLStreamReader(游标)API 读取 XML 文件。

  • 游标到事件示例cursor2event目录包含CursorApproachEventObject.java,演示应用程序如何在使用游标 API 时将信息作为XMLEvent对象获取。

  • 事件示例event目录包含EventParse.java,演示如何使用XMLEventReader(事件迭代器)API 读取 XML 文件。

  • 过滤器示例filter目录包含MyStreamFilter.java,演示如何使用 StAX 流过滤器 API。在此示例中,过滤器仅接受StartElementEndElement事件,并过滤掉其余事件。

  • 读写示例readnwrite目录包含EventProducerConsumer.java,演示了如何使用 StAX 生产者/消费者机制同时读取和写入 XML 流。

  • 写入示例writer目录包含CursorWriter.java,演示如何使用XMLStreamWriter以编程方式编写 XML 文件。

除了写入示例外,本节中的所有 StAX 示例均使用示例 XML 文档BookCatalog.xml

示例 XML 文档

大多数 StAX 示例类使用的示例 XML 文档BookCatalog.xml是一个基于常见BookCatalogue命名空间的简单图书目录。BookCatalog.xml的内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<BookCatalogue >
<Book>
    <Title>Yogasana Vijnana: the Science of Yoga</Title>
    <author>Dhirendra Brahmachari</Author>
    <Date>1966</Date>
    <ISBN>81-40-34319-4</ISBN>
    <Publisher>Dhirendra Yoga Publications</Publisher>
    <Cost currency="INR">11.50</Cost>
</Book>
<Book>
    <Title>The First and Last Freedom</Title>
    <Author>J. Krishnamurti</Author>
    <Date>1954</Date>
    <ISBN>0-06-064831-7</ISBN>
    <Publisher>Harper &amp; Row</Publisher>
    <Cost currency="USD">2.95</Cost>
</Book>
</BookCatalogue>

游标示例

位于INSTALL_DIR/jaxp-version/samples/stax/cursor/目录中,CursorParse.java演示了如何使用 StAX 游标 API 读取 XML 文档。在游标示例中,应用程序通过调用next()指示解析器读取 XML 输入流中的下一个事件。

请注意,next()只返回与解析器所处位置对应的整数常量。应用程序需要调用相关函数以获取与底层事件相关的更多信息。

您可以将这种方法想象成虚拟游标在 XML 输入流中移动。当虚拟游标位于特定事件时,可以调用各种访问器方法。

逐个事件地进行步进

在这个示例中,客户端应用程序通过在解析器上调用next方法来拉取 XML 流中的下一个事件;例如:

try {
    for (int i = 0 ; i < count ; i++) {
        // pass the file name.. all relative entity
        // references will be resolved against this 
        // as base URI.
        XMLStreamReader xmlr = xmlif.createXMLStreamReader(filename,
                                   new FileInputStream(filename));

        // when XMLStreamReader is created, 
        // it is positioned at START_DOCUMENT event.
        int eventType = xmlr.getEventType();
        printEventType(eventType);
        printStartDocument(xmlr);

        // check if there are more events 
        // in the input stream
        while(xmlr.hasNext()) {
            eventType = xmlr.next();
            printEventType(eventType);

            // these functions print the information 
            // about the particular event by calling 
            // the relevant function
            printStartElement(xmlr);
            printEndElement(xmlr);
            printText(xmlr);
            printPIData(xmlr);
            printComment(xmlr);
        }
    }
}

请注意,next只是返回与当前游标位置下的事件对应的整数常量。应用程序调用相关函数以获取与底层事件相关的更多信息。当游标位于特定事件时,可以调用各种访问器方法。

返回字符串表示形式

因为next方法只返回与底层事件类型对应的整数,通常需要将这些整数映射到事件的字符串表示形式;例如:

public final static String getEventTypeString(int eventType) {
    switch (eventType) {
        case XMLEvent.START_ELEMENT:
            return "START_ELEMENT";

        case XMLEvent.END_ELEMENT:
            return "END_ELEMENT";

        case XMLEvent.PROCESSING_INSTRUCTION:
            return "PROCESSING_INSTRUCTION";

        case XMLEvent.CHARACTERS:
            return "CHARACTERS";

        case XMLEvent.COMMENT:
            return "COMMENT";

        case XMLEvent.START_DOCUMENT:
            return "START_DOCUMENT";

        case XMLEvent.END_DOCUMENT:
            return "END_DOCUMENT";

        case XMLEvent.ENTITY_REFERENCE:
            return "ENTITY_REFERENCE";

        case XMLEvent.ATTRIBUTE:
            return "ATTRIBUTE";

        case XMLEvent.DTD:
            return "DTD";

        case XMLEvent.CDATA:
            return "CDATA";

        case XMLEvent.SPACE:
            return "SPACE";
    }
    return "UNKNOWN_EVENT_TYPE , " + eventType;
}

运行游标示例

  1. 要编译和运行游标示例,在终端窗口中,转到INSTALL_DIR/jaxp-version/samples/目录,并输入以下内容:

    javac stax/cursor/*.java
    
    
  2. BookCatalogue.xml文件上运行CursorParse示例,使用以下命令。

    CursorParse将打印出BookCatalogue.xml文件的每个元素。

java stax/event/CursorParse stax/data/BookCatalogue.xml

游标到事件示例

位于tut-install/javaeetutorial5/examples/stax/cursor2event/目录中,CursorApproachEventObject.java演示了如何在使用游标 API 时获取XMLEvent对象返回的信息。

这里的想法是,游标 API 的XMLStreamReader返回与特定事件对应的整数常量,而事件迭代器 API 的XMLEventReader返回不可变且持久的事件对象。 XMLStreamReader更有效率,但XMLEventReader更易于使用,因为与特定事件相关的所有信息都封装在返回的XMLEvent对象中。然而,事件方法的缺点是为每个事件创建对象的额外开销,这既消耗时间又消耗内存。

有了这个想法,即使使用游标 API,也可以使用XMLEventAllocator来获取事件信息作为XMLEvent对象。

实例化一个 XMLEventAllocator

第一步是创建一个新的XMLInputFactory并实例化一个XMLEventAllocator

XMLInputFactory xmlif = XMLInputFactory.newInstance();
System.out.println("FACTORY: " + xmlif);
xmlif.setEventAllocator(new XMLEventAllocatorImpl());
allocator = xmlif.getEventAllocator();
XMLStreamReader xmlr = xmlif.createXMLStreamReader(filename,
                           new FileInputStream(filename));

创建一个事件迭代器

下一步是创建一个事件迭代器:

int eventType = xmlr.getEventType();

while (xmlr.hasNext()) {
    eventType = xmlr.next();
    // Get all "Book" elements as XMLEvent object
    if (eventType == XMLStreamConstants.START_ELEMENT 
        && xmlr.getLocalName().equals("Book")) {
        // get immutable XMLEvent
        StartElement event = getXMLEvent(xmlr).asStartElement();
        System.out.println ("EVENT: " + event.toString());
    }
}

创建分配器方法

最后一步是创建XMLEventAllocator方法:

private static XMLEvent getXMLEvent(XMLStreamReader reader)
    throws XMLStreamException {
    return allocator.allocate(reader);
}

运行游标到事件示例

  1. 要编译和运行游标到事件示例,在终端窗口中,转到INSTALL_DIR/jaxp-version/samples/目录,并输入以下内容:

    javac -classpath ../lib/jaxp-ri.jar stax/cursor2event/*.java
    
    
  2. BookCatalogue.xml文件上运行CursorApproachEventObject示例,使用以下命令。

    java stax/cursor2event/CursorApproachEventObject stax/data/BookCatalogue.xml
    
    

    CursorApproachEventObject将打印出BookCatalogue.xml文件中定义的事件列表。

事件示例

位于INSTALL_DIR/jaxp-version/samples/stax/event/目录中,EventParse.java演示了如何使用 StAX 事件 API 读取 XML 文档。

创建一个输入工厂

第一步是创建一个新的XMLInputFactory实例:

XMLInputFactory factory = XMLInputFactory.newInstance();
System.out.println("FACTORY: " + factory);

创建一个事件读取器

下一步是创建一个 XMLEventReader 实例:

XMLEventReader r = factory.createXMLEventReader
                       (filename, new FileInputStream(filename));

创建一个事件迭代器

第三步是创建一个事件迭代器:

XMLEventReader r = factory.createXMLEventReader
                       (filename, new FileInputStream(filename));
while (r.hasNext()) {
    XMLEvent e = r.nextEvent();
    System.out.println(e.toString());
}

获取事件流

最后一步是获取底层事件流:

public final static String getEventTypeString(int eventType) {
    switch (eventType) {
        case XMLEvent.START_ELEMENT:
            return "START_ELEMENT";

        case XMLEvent.END_ELEMENT:
            return "END_ELEMENT";

        case XMLEvent.PROCESSING_INSTRUCTION:
            return "PROCESSING_INSTRUCTION";

        case XMLEvent.CHARACTERS:
            return "CHARACTERS";

        case XMLEvent.COMMENT:
            return "COMMENT";

        case XMLEvent.START_DOCUMENT:
            return "START_DOCUMENT";

        case XMLEvent.END_DOCUMENT:
            return "END_DOCUMENT";

        case XMLEvent.ENTITY_REFERENCE:
            return "ENTITY_REFERENCE";

        case XMLEvent.ATTRIBUTE:
            return "ATTRIBUTE";

        case XMLEvent.DTD:
            return "DTD";

        case XMLEvent.CDATA:
            return "CDATA";

        case XMLEvent.SPACE:
            return "SPACE";
    }
    return "UNKNOWN_EVENT_TYPE," + eventType;
}

返回输出

当你运行事件示例时,EventParse 类被编译,XML 流被解析为事件并返回到 STDOUT。例如,Author 元素的一个实例被返回为:

<[’http://www.publishing.org’]::Author>
    Dhirendra Brahmachari
</[’http://www.publishing.org’]::Author>

请注意,在这个示例中,事件包括一个包含命名空间的开标签和闭标签,两者都包含元素的内容作为字符串返回在标签内。

同样,一个 Cost 元素的一个实例被返回如下:

<[’http://www.publishing.org’]::Cost currency=’INR’>
    11.50
</[’http://www.publishing.org’]::Cost

在这种情况下,currency 属性和值在事件的开标签中返回。

运行事件示例

  1. 要编译和运行事件示例,在终端窗口中,转到 INSTALL_DIR/jaxp-version/samples/ 目录并输入以下内容:

    javac -classpath ../lib/jaxp-ri.jar stax/event/*.java
    
    
  2. BookCatalogue.xml 文件运行 EventParse 示例,使用以下命令。

    java stax/event/EventParse stax/data/BookCatalogue.xml
    
    

    EventParse 将打印出由 BookCatalogue.xml 文件定义的所有元素的数据。

过滤器示例

位于 INSTALL_DIR/jaxp-version/samples/stax/filter/ 目录中,MyStreamFilter.java 演示了如何使用 StAX 流过滤器 API 过滤应用程序不需要的事件。在这个示例中,解析器过滤掉除了 StartElementEndElement 之外的所有事件。

实现 StreamFilter 类

MyStreamFilter 类实现了 javax.xml.stream.StreamFilter

public class MyStreamFilter implements javax.xml.stream.StreamFilter {
    // ...
}

创建一个输入工厂

下一步是创建一个 XMLInputFactory 实例。在这种情况下,还在工厂上设置了各种属性:

XMLInputFactory xmlif = null ;

try {
    xmlif = XMLInputFactory.newInstance();
    xmlif.setProperty(
        XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES,
        Boolean.TRUE);

    xmlif.setProperty(
        XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES,
        Boolean.FALSE);

    xmlif.setProperty(
        XMLInputFactory.IS_NAMESPACE_AWARE,
        Boolean.TRUE);

    xmlif.setProperty(
        XMLInputFactory.IS_COALESCING,
        Boolean.TRUE);
} 
catch (Exception ex) {
    ex.printStackTrace();
}

System.out.println("FACTORY: " + xmlif);
System.out.println("filename = "+ filename);

创建过滤器

下一步是实例化一个文件输入流并创建流过滤器:

FileInputStream fis = new FileInputStream(filename);
XMLStreamReader xmlr = xmlif.createFilteredReader(
                           xmlif.createXMLStreamReader(fis), 
                           new MyStreamFilter());

int eventType = xmlr.getEventType();
printEventType(eventType);

while (xmlr.hasNext()) {
    eventType = xmlr.next();
    printEventType(eventType);
    printName(xmlr,eventType);
    printText(xmlr);

    if (xmlr.isStartElement()) {
        printAttributes(xmlr);
    }
    printPIData(xmlr);
    System.out.println("-----------------------");
}

捕获事件流

下一步是捕获事件流。这与事件示例中的方式基本相同。

过滤流

最后一步是过滤流:

public boolean accept(XMLStreamReader reader) {
    if (!reader.isStartElement() && !reader.isEndElement())
        return false;
    else
        return true;
}

返回输出

当你运行过滤器示例时,MyStreamFilter 类被编译,XML 流被解析为事件并返回到 STDOUT。例如,一个 Author 事件被返回如下:

EVENT TYPE(1):START_ELEMENT
HAS NAME: Author
HAS NO TEXT
HAS NO ATTRIBUTES
-----------------------------
EVENT TYPE(2):END_ELEMENT
HAS NAME: Author
HAS NO TEXT
-----------------------------

同样,一个 Cost 事件被返回如下:

EVENT TYPE(1):START_ELEMENT
HAS NAME: Cost
HAS NO TEXT

HAS ATTRIBUTES:
 ATTRIBUTE-PREFIX:
 ATTRIBUTE-NAMESP: null
ATTRIBUTE-NAME:   currency
ATTRIBUTE-VALUE: USD
ATTRIBUTE-TYPE:  CDATA

-----------------------------
EVENT TYPE(2):END_ELEMENT
HAS NAME: Cost
HAS NO TEXT
-----------------------------

查看 迭代器 API 和 读取 XML 流 以获取更详细的 StAX 事件解析讨论。

运行过滤器示例

  1. 要编译和运行过滤器示例,在终端窗口中,转到 INSTALL_DIR/jaxp-version/samples/ 目录并输入以下内容:

    javac -classpath ../lib/jaxp-ri.jar stax/filter/*.java
    
    
  2. java.endorsed.dirs 系统属性设置为指向 samples/lib 目录的情况下,对 BookCatalogue.xml 文件运行 MyStreamFilter 示例,使用以下命令。

    java -Djava.endorsed.dirs=../lib stax/filter/MyStreamFilter -f stax/data/BookCatalogue.xml
    
    

    MyStreamFilter 将打印出由 BookCatalogue.xml 文件定义的事件作为 XML 流。

读写示例

位于INSTALL_DIR/jaxp-version/samples/stax/readnwrite/目录中,EventProducerConsumer.java演示了如何同时将 StAX 解析器用作生产者和消费者。

StAX XMLEventWriter API 扩展自XMLEventConsumer接口,并被称为事件消费者。相比之下,XMLEventReader是一个事件生产者。StAX 支持同时读取和写入,因此可以顺序地从一个 XML 流中读取并同时写入到另一个流中。

读写示例展示了如何使用 StAX 生产者/消费者机制同时读取和写入。该示例还展示了如何修改流以及如何动态添加新事件,然后写入到不同的流中。

创建一个事件生产者/消费者

第一步是实例化一个事件工厂,然后创建一个事件生产者/消费者的实例:

XMLEventFactory m_eventFactory = XMLEventFactory.newInstance();

public EventProducerConsumer() {
    // ...
    try {
        EventProducerConsumer ms = new EventProducerConsumer();

        XMLEventReader reader = XMLInputFactory.newInstance().
        createXMLEventReader(new java.io.FileInputStream(args[0]));

        XMLEventWriter writer = 
            XMLOutputFactory.newInstance().createXMLEventWriter(System.out);
    }
    // ...
}

创建一个迭代器

下一步是创建一个迭代器来解析流:

while (reader.hasNext()) {
    XMLEvent event = (XMLEvent)reader.next();
    if (event.getEventType() == event.CHARACTERS) {
        writer.add(ms.getNewCharactersEvent(event.asCharacters()));
    }
    else {
        writer.add(event);
    }
}
writer.flush();

创建一个写入器

最后一步是创建一个流写入器,形式为一个新的Character事件:

Characters getNewCharactersEvent(Characters event) {
    if (event.getData().equalsIgnoreCase("Name1")) {
        return m_eventFactory.createCharacters(
                   Calendar.getInstance().getTime().toString());
    }
    // else return the same event
    else {
        return event;
    }
}

返回输出

运行读写示例时,EventProducerConsumer类被编译,并且 XML 流被解析为事件并写回到STDOUT。输出是示例 XML 文档中描述的BookCatalog.xml文件的内容。

运行读写示例

  1. 要编译和运行读写示例,在终端窗口中,转到INSTALL_DIR/jaxp-version/samples/目录并输入以下内容:

    javac -classpath ../lib/jaxp-ri.jar stax/readnwrite/*.java
    
    
  2. BookCatalogue.xml文件上运行EventProducerConsumer示例,使用以下命令。

    java stax/readnwrite/EventProducerConsumer stax/data/BookCatalogue.xml
    
    

    EventProducerConsumer将打印出BookCatalogue.xml文件的内容。

写入器示例

位于INSTALL_DIR/jaxp-version/samples/stax/writer/目录中,CursorWriter.java演示了如何使用 StAX 游标 API 编写 XML 流。

创建输出工厂

第一步是创建一个XMLOutputFactory的实例:

XMLOutputFactory xof =  XMLOutputFactory.newInstance();

创建一个流写入器

下一步是创建一个XMLStreamWriter的实例:

XMLStreamWriter xtw = null;

写入流

最后一步是写入 XML 流。请注意,在写入最终的EndDocument后,流会被刷新并关闭:

xtw = xof.createXMLStreamWriter(new FileWriter(fileName));

xtw.writeComment("all elements here are explicitly in the HTML namespace");
xtw.writeStartDocument("utf-8","1.0");
xtw.setPrefix("html", "http://www.w3.org/TR/REC-html40");
xtw.writeStartElement("http://www.w3.org/TR/REC-html40","html");
xtw.writeNamespace("html", "http://www.w3.org/TR/REC-html40");
xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "head");
xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "title");
xtw.writeCharacters("Frobnostication");
xtw.writeEndElement();
xtw.writeEndElement();

xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "body");
xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "p");
xtw.writeCharacters("Moved to");
xtw.writeStartElement("http://www.w3.org/TR/REC-html40", "a");
xtw.writeAttribute("href","http://frob.com");
xtw.writeCharacters("here");
xtw.writeEndElement();
xtw.writeEndElement();
xtw.writeEndElement();
xtw.writeEndElement();
xtw.writeEndDocument();

xtw.flush();
xtw.close();

返回输出

运行写入器示例时,CursorWriter类被编译,并且 XML 流被解析为事件并写入到名为dist/CursorWriter-Output的文件中:

<!--all elements here are explicitly in the HTML namespace-->
<?xml version="1.0" encoding="utf-8"?>
<html:html >
<html:head>
<html:title>Frobnostication</html:title></html:head>
<html:body>
<html:p>Moved to <html:a href="http://frob.com">here</html:a>
</html:p>
</html:body>
</html:html>

在实际的dist/CursorWriter-Output文件中,该流是连续写入的,没有任何换行符;这里添加了换行符以便更容易阅读清单。在这个示例中,与事件示例中的对象流一样,命名空间前缀被添加到 HTML 标签的开头和结尾。虽然 StAX 规范不要求添加这个前缀,但是当输出流的最终范围不明确时,这是一个良好的实践。

运行写入器示例

  1. 要编译和运行 Writer 示例,在终端窗口中,转到 INSTALL_DIR/jaxp-version/samples/ 目录,并输入以下内容:

    javac -classpath \
        ../lib/jaxp-ri.jar stax/writer/*.java
    
    
  2. 运行 CursorWriter 示例,指定输出应写入的文件名。

    java stax/writer/CursorWriter -f *output_file*
    
    

    CursorWriter 将创建一个包含 返回输出 中显示的数据的相应名称的输出文件。

更多信息

docs.oracle.com/javase/tutorial/jaxp/stax/info.html

有关 StAX 的更多信息,请参见:

有关使用 StAX 的一些有用文章,请参见:

课程:JAXP 1.5 和新属性

原文:docs.oracle.com/javase/tutorial/jaxp/properties/index.html

本课程重点介绍了 JAXP 1.5 中引入的新属性。

JAXP 1.5 被添加到了 7u40 和 JDK 8 版本中。你可以从java.net下载当前的JDK 8 快照

背景

原文:docs.oracle.com/javase/tutorial/jaxp/properties/backgnd.html

JAXP 安全处理功能对 XML 处理器施加资源限制,以抵御某些类型的拒绝服务攻击。 但是,它并不限制获取外部资源的方式,这在尝试安全处理 XML 文档时也是有用的。 当前的 JAXP 实现支持特定于实现的属性,可用于强制执行此类限制,但需要一种标准方法来实现。

JAXP 1.5 添加了三个新属性以及它们对应的系统属性,允许用户指定可以或不可以允许的外部连接类型。属性值是协议列表。 JAXP 处理器通过将协议与列表中的协议进行匹配来检查给定的外部连接是否被允许。 如果连接在列表中,则处理器将尝试建立连接,否则将拒绝连接。

JAXP 1.5 已经集成到 7u40 和 JDK8 中。

外部资源

原文:docs.oracle.com/javase/tutorial/jaxp/properties/resources.html

XML、Schema 和 XSLT 标准支持以下需要外部资源的构造。JDK XML 处理器的默认行为是建立连接并按照指定的方式获取外部资源。

  • 外部 DTD:引用外部文档类型定义(DTD),示例:<!DOCTYPE root_element SYSTEM "url">

  • 外部实体引用:引用外部数据,语法:<!ENTITY name SYSTEM "url">

    通用实体引用如下:

    
    <?xml version="1.0" standalone="no" ?>
    <!DOCTYPE doc [<!ENTITY otherFile SYSTEM "otherFile.xml">]>
    <doc>
        <foo>
        <bar>&otherFile;</bar>
        </foo>
    </doc>
    
    
  • 外部参数实体,语法 <!ENTITY % name SYSTEM uri>。例如:

    <?xml version="1.0" standalone="no"?>
        <!DOCTYPE doc [
          <!ENTITY % foo SYSTEM "http://www.example.com/student.dtd"<
          %foo;
        ]>
    
    
  • XInclude:在 XML 文档中包含外部信息集

  • 使用 schemaLocation 属性、importinclude 元素引用 XML Schema 组件。示例:schemaLocation="http://www.example.com/schema/bar.xsd"

  • 使用 importinclude 元素合并样式表:语法:<xsl:include href="include.xsl"/>

  • xml-stylesheet 处理指令:用于在 xml 文档中包含样式表,语法:<?xml-stylesheet href="foo.xsl" type="text/xsl"?>

  • XSLT document() 函数:用于访问外部 XML 文档中的节点。例如,<xsl:variable name="dummy" select="document('DocumentFunc2.xml')/>

新属性

原文:docs.oracle.com/javase/tutorial/jaxp/properties/properties.html

JAXP 1.5 定义了三个新属性,用于调节 XML 处理器是否解析上述外部资源。这些属性是:

  • javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD

  • javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA

  • javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET

这些 API 属性具有相应的系统属性和 jaxp.properties。

ACCESS_EXTERNAL_DTD

名称http://javax.xml.XMLConstants/property/accessExternalDTD

定义:限制对外部 DTD、外部实体引用到指定协议的访问。

:参见属性的值

默认值all,允许连接到所有协议。

系统属性javax.xml.accessExternalDTD

ACCESS_EXTERNAL_SCHEMA

名称http://javax.xml.XMLConstants/property/accessExternalSchema

定义:限制对由schemaLocation属性、Import 和 Include 元素设置的外部引用协议的访问。

:参见属性的值

默认值all,允许连接到所有协议。

系统属性javax.xml.accessExternalSchema

ACCESS_EXTERNAL_STYLESHEET

名称http://javax.xml.XMLConstants/property/accessExternalStylesheet

定义:限制对由样式表处理指令、文档函数、Import 和 Include 元素设置的外部引用协议的访问。

:参见属性的值

默认值all,允许连接到所有协议。

系统属性javax.xml.accessExternalStylesheet

${java.home}/lib/jaxp.properties

这些属性可以在jaxp.properties中指定,以定义所有使用 Java Runtime 的应用程序的行为。格式为property-name=[value][,value]*。例如:

javax.xml.accessExternalDTD=file,http

属性名称与系统属性相同:javax.xml.accessExternalDTDjavax.xml.accessExternalSchemajavax.xml.accessExternalStylesheet

属性的值

所有属性的值格式相同。

:由逗号分隔的协议列表。协议是 URI 的 scheme 部分,或者在 JAR 协议的情况下,由冒号分隔的"jar"加上 scheme 部分。协议定义为:

scheme = alpha *( alpha | digit | "+" | "-" | "." )

其中 alpha = a-z 和 A-Z。

以及 JAR 协议:

jar[:scheme]

协议不区分大小写。值中由Character.isSpaceChar定义的任何空格将被忽略。协议的示例包括filehttpjar:file

默认值:默认值是实现特定的。在 JAXP 1.5 RI、Java SE 7u40 和 Java SE 8 中,默认值为all,授予所有协议的权限。

授予所有访问:关键字all授予所有协议的权限。例如,在jaxp.properties中设置javax.xml.accessExternalDTD=all将允许系统像以前一样工作,无限制地访问外部 DTD 和实体引用。

拒绝任何访问:空字符串,即"",表示不授予任何协议权限。例如,在jaxp.properties中设置javax.xml.accessExternalDTD=""将指示 JAXP 处理器拒绝任何外部连接。

范围和顺序

原文:docs.oracle.com/javase/tutorial/jaxp/properties/scope.html

javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING(FSP)是包括 DOM、SAX、Schema Validation、XSLT 和 XPath 在内的 XML 处理器的必需功能。当设置为true时,建议实现启用由上述新属性定义的访问限制。为了兼容性,尽管对于 DOM、SAX 和 Schema Validation,默认情况下 FSP 为 true,但 JAXP 1.5 不会启用新的限制。

对于 JDK 8,建议将新的accessExternal*属性在 FSP 被明确设置时设置为空字符串。这仅在通过 API 设置 FSP 时才会发生,例如factory.setFeature(FSP, true)。尽管对于 DOM、SAX 和 Schema Validation,默认情况下 FSP 为 true,但 JDK 8 并不将其视为“明确”设置,因此默认情况下不会设置限制。

jaxp.properties文件中指定的属性会影响 JDK 或 JRE 的所有调用,并将覆盖其默认值,或者可能已经由 FEATURE_SECURE_PROCESSING 设置的值。

当设置系统属性时,将仅影响一个调用,并将覆盖默认设置或在 jaxp.properties 中设置的设置,或者可能已经由 FEATURE_SECURE_PROCESSING 设置的设置。

通过 JAXP 工厂或SAXParser指定的 JAXP 属性优先于系统属性,jaxp.properties文件以及javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING

新的 JAXP 属性在以下情况下对其试图限制的相关构造没有影响:

  • 当存在解析器并且解析器返回的源不为 null 时。这适用于可能设置在 SAX 和 DOM 解析器上的实体解析器,StAX 解析器上的 XML 解析器,SchemaFactory 上的 LSResourceResolver,验证器或 ValidatorHandler,或者转换器上的 URIResolver。

  • 当通过调用SchemaFactorynewSchema方法显式创建模式时。

  • 当不需要外部资源时。例如,以下功能/属性由参考实现支持,并可用于指示处理器不加载外部 DTD 或解析外部实体。

    http://apache.org/xml/features/disallow-doctype-decl true
    http://apache.org/xml/features/nonvalidating/load-external-dtd false
    http://xml.org/sax/features/external-general-entities false
    http://xml.org/sax/features/external-parameter-entities false
    
    

与安全管理器的关系

原文:docs.oracle.com/javase/tutorial/jaxp/properties/security.html

在尝试连接之前,将首先检查 JAXP 属性,无论是否存在SecurityManager。这意味着即使SecurityManager授予权限,连接也可能被阻止。例如,如果 JAXP 属性被设置为禁止 http 协议,它们将有效地阻止任何连接尝试,即使应用程序具有SocketPermission

为了限制连接,SecurityManager可以被视为处于较低级别。在评估 JAXP 属性之后,权限将被检查。例如,如果一个应用程序没有SocketPermission,即使 JAXP 属性被设置为允许 http 连接,也会抛出SecurityException

当存在SecurityManager时,JAXP FEATURE_SECURE_PROCESSING被设置为 true。这种行为不会启用新的限制。

JDK 中的属性设置

原文:docs.oracle.com/javase/tutorial/jaxp/properties/propSettings.html

以下表格显示了 JDK 中新属性的默认值和行为。

访问属性的值 默认值 设置 FSP(a) jaxp.properties 系统属性 API 属性
7u40 all 无更改 覆盖 覆盖 覆盖
JDK8 all 更改为 "" 覆盖 覆盖 覆盖

(a) 设置 FSP 意味着明确使用 JAXP 工厂的 setFeature 方法设置 FEATURE_SECURE_PROCESSING。

(b) 7u40 和 JDK8 之间唯一的行为差异是,在 7u40 中设置 FSP 不会更改 accessExternal* 属性,但在 JDK8 中会将值设置为空字符串。在 JDK8 中,设置 FSP 被视为选择加入。

(c) 表中从左到右的顺序反映了覆盖顺序。例如,如果通过 API 设置了 accessExternal 属性,则会覆盖其他可能已通过其他方式设置的属性。

使用属性

译文:docs.oracle.com/javase/tutorial/jaxp/properties/usingProps.html

本节重点介绍了 JAXP 1.5 中引入的新属性。

何时使用属性

只有处理不受信任的 XML 内容的应用程序才需要限制获取外部资源。不处理不受信任内容的内部系统和应用程序不需要关注新的限制或进行任何更改。自 7u40 和 JDK8 默认没有对此类限制的要求,应用程序在升级到 7u40 和 JDK8 时不会出现行为变化。

对于处理不受信任的 XML 输入、Schema 或样式表的应用程序,如果已经存在安全措施,比如启用 Java 安全管理器仅授予受信任的外部连接,或者使用解析器解析实体,则不需要 JAXP 1.5 中添加的新功能。

然而,JAXP 1.5 确实为没有安全管理器运行的系统和应用程序提供了直接的保护。对于这类应用程序,可以考虑使用下面详细描述的新功能来进行限制。

通过 API 设置属性

当改变代码可行时,通过 JAXP 工厂或解析器设置新属性是启用限制的最佳方式。属性可以通过以下接口设置:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setAttribute(name, value);

SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
parser.setProperty(name, value);

XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(name, value);

SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
schemaFactory.setProperty(name, value);

TransformerFactory factory = TransformerFactory.newInstance();
factory.setAttribute(name, value);

以下是一个将 DOM 解析器限制为仅本地连接的外部 DTD 的示例:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try {
    dbf.setAttribute({{XMLConstants.ACCESS_EXTERNAL_DTD}}, "file, jar:file");
} catch (IllegalArgumentException e) {
    //jaxp 1.5 feature not supported
}

当代码更改可行,并且对于新开发,建议设置新属性如上所示。通过这种方式设置属性,应用程序可以确保无论部署到较旧还是较新版本的 JDK,或者通过系统属性或jaxp.properties设置属性,都能保持所需的行为。

使用系统属性

如果改变代码不可行,系统属性可能会有用。

如果希望为整个 JDK/JRE 调用设置限制,可以在命令行上设置系统属性;如果仅需要部分应用程序,可以在该部分之前设置系统属性,然后在之后清除。例如,以下代码展示了如何使用系统属性:

//allow resolution of external schemas

System.setProperty("javax.xml.accessExternalSchema", "file, http");

//this setting will affect all processing after it's set
some processing here

//after it's done, clear the property
System.clearProperty("javax.xml.accessExternalSchema");

使用 jaxp.properties

jaxp.properties 是一个普通的配置文件。它位于${java.home}/lib/jaxp.properties,其中 java.home 是 JRE 安装目录,例如,[安装目录路径]/jdk7/jre

可通过将以下行添加到 jaxp.properties 文件来设置外部访问限制:

javax.xml.accessExternalStylesheet=file, http

设置此项后,所有 JDK/JRE 的调用将遵守加载外部样式表的限制。

对于不希望允许 XML 处理器进行任何外部连接的系统,此功能可能很有用,此时,所有三个属性可以设置为,例如,仅文件。

错误处理

原文:docs.oracle.com/javase/tutorial/jaxp/properties/error.html

由于这些属性是当前版本的新功能,建议应用程序捕获适合接口的异常,例如,在以下示例中捕获 SAXException。在旧版本上,应用程序可能正常工作,例如,示例代码包含以下方法,用于检测是否使用支持新属性的 JDK 版本或 JAXP 实现运行示例:

public boolean isNewPropertySupported() {
       try {
           SAXParserFactory spf = SAXParserFactory.newInstance();
           SAXParser parser = spf.newSAXParser();
           parser.setProperty("http://javax.xml.XMLConstants/property/accessExternalDTD", "file");
       } catch (ParserConfigurationException ex) {
           fail(ex.getMessage());
       } catch (SAXException ex) {
           String err = ex.getMessage();
           if (err.indexOf("Property 'http://javax.xml.XMLConstants/property/accessExternalDTD' is not recognized.") > -1)
           {
             //expected, jaxp 1.5 not supported
             return false;
           }
       }
       return true;
  }

如果由于新属性设置的限制而拒绝访问外部资源,则将以以下格式抛出异常并带有错误信息:

[type of construct]: Failed to read [type of construct] "[name of the external resource]", because "[type of restriction]" access is not allowed due to restriction set by the [property name] property.

例如,如果由于限制只允许使用 http 协议而拒绝获取外部 DTD,如下所示:parser.setProperty("http://javax.xml.XMLConstants/property/accessExternalDTD", "file");,并且解析器解析包含对"http://java.sun.com/dtd/properties.dtd"的外部引用的 XML 文件,则错误消息将如下所示:

External DTD: Failed to read external DTD ''http://java.sun.com/dtd/properties.dtd'', because ''http'' access is not allowed due to restriction set by the accessExternalDTD property.

StAX

原文:docs.oracle.com/javase/tutorial/jaxp/properties/stax.html

StAX、JSR 173 的规范尚不支持新属性。然而,在 JAXP 的上下文中,StAX 确实包括对这些属性的支持。设置新属性类似于 SAX 或 DOM,但通过 XMLInputFactory,如下所示:

XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty("http://javax.xml.XMLConstants/property/accessExternalDTD", "file");

存在于 StAX、JSR 173 规范中指定的属性和特性将优先于新的 JAXP 属性。例如,当 SupportDTD 属性设置为 false 时,将导致程序在输入文件包含 DTD 之前无法解析时抛出异常。对于使用 SupportDTD 属性禁用 DTD 的应用程序,新属性的添加不会产生影响。

结论

原文:docs.oracle.com/javase/tutorial/jaxp/properties/conclusion.html

JAXP 1.5 提供了新的属性,让用户可以控制获取外部资源。使用新属性与其他现有属性相同,只是这些属性与相应的系统属性和jaxp.properties一起提供,以便它们可以用于系统范围的限制或权限。

参考资料

原文:docs.oracle.com/javase/tutorial/jaxp/properties/references.html

欲了解更多信息,请参阅以下资源:

Lesson: 处理限制

原文:docs.oracle.com/javase/tutorial/jaxp/limits/index.html

XML 处理有时可能是一个消耗大量内存的操作。应用程序,特别是那些接受来自不受信任来源的 XML、XSD 和 XSL 的应用程序,应该通过使用 JDK 提供的 JAXP 处理限制来防范过度的内存消耗。

开发人员应该评估他们应用程序的需求和运行环境,以确定系统配置的可接受限制,并相应地设置这些限制。与大小相关的限制可用于防止处理畸形的 XML 源时消耗大量内存,而EntityExpansionLimit将允许应用程序在可接受水平下控制内存消耗。

在本教程中,您将了解这些限制,并学习如何正确使用它们。

处理限制定义

原文:docs.oracle.com/javase/tutorial/jaxp/limits/limits.html

以下列表描述了 JDK 支持的 JAXP XML 处理限制。这些限制可以通过工厂 API、系统属性和jaxp.properties文件指定。

entityExpansionLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit
定义 限制实体扩展的数量。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 64000
系统属性 jdk.xml.entityExpansionLimit
自从 7u45, 8

elementAttributeLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/elementAttributeLimit
定义 限制元素可以拥有的属性数量。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 10000
系统属性 jdk.xml.elementAttributeLimit
自从 7u45, 8

maxOccurLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/maxOccurLimit
定义 限制在构建包含值不是"unbounded"的maxOccurs属性的 W3C XML Schema 的语法时可以创建的内容模型节点的数量。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 5000
系统属性 jdk.xml.maxOccurLimit
自从 7u45, 8

totalEntitySizeLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/totalEntitySizeLimit
定义 限制包含通用实体和参数实体的所有实体的总大小。大小是所有实体的聚合计算。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 5x10⁷
系统属性 jdk.xml.totalEntitySizeLimit
自从 7u45, 8

maxGeneralEntitySizeLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/maxGeneralEntitySizeLimit
定义 限制任何通用实体的最大大小。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 0
系统属性 jdk.xml.maxGeneralEntitySizeLimit
自从 7u45, 8

maxParameterEntitySizeLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/maxParameterEntitySizeLimit
定义 限制任何参数实体的最大大小,包括嵌套多个参数实体的结果。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 1000000
系统属性 jdk.xml.maxParameterEntitySizeLimit
自 JDK 7u45, 8 起

entityReplacementLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/entityReplacementLimit
定义 限制所有实体引用中节点的总数。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 3000000
系统属性 jdk.xml.entityReplacementLimit
自 JDK 7u111, 8u101 起

maxElementDepth

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/maxElementDepth
定义 限制最大元素深度。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 0
系统属性 jdk.xml.maxElementDepth
自 JDK 7u65, 8u11 起

maxXMLNameLimit

属性 描述
名称 http://www.oracle.com/xml/jaxp/properties/maxXMLNameLimit
定义 限制 XML 名称的最大大小,包括元素名称、属性名称和命名空间前缀和 URI。
一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。
默认值 1000
系统属性 jdk.xml.maxXMLNameLimit
自 JDK 7u91, 8u65 起

旧版系统属性

这些属性自 JDK 5.0 和 6 起被引入,并继续为向后兼容性而受支持。

系统属性 自 JDK 5.0 和 6 起 新系统属性
entityExpansionLimit 1.5 jdk.xml.entityExpansionLimit
elementAttributeLimit 1.5 jdk.xml.elementAttributeLimit
maxOccurLimit 1.6 jdk.xml.maxOccur

{java.home}/lib/jaxp.properties

可以在jaxp.properties文件中指定系统属性,以定义 JDK 或 JRE 的所有调用的行为。格式为system-property-name=value。例如:

jdk.xml.maxGeneralEntitySizeLimit=1024

范围和顺序

原文:docs.oracle.com/javase/tutorial/jaxp/limits/scope.html

javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING(FSP)功能对包括 DOM、SAX、模式验证、XSLT 和 XPath 在内的 XML 处理器是必需的。当 FSP 设置为true时,建议的默认限制将被强制执行。将 FSP 设置为false不会改变这些限制。

当 Java 安全管理器存在时,FSP 被设置为 true 且无法关闭。因此,建议的默认限制将被强制执行。

jaxp.properties文件中指定的属性会影响 JDK 和 JRE 的所有调用,并将覆盖它们的默认值,或者可能已被 FSP 设置的值。

系统属性在设置时会影响 JDK 和 JRE 的调用,并覆盖默认设置或者在jaxp.properties中设置的值,或者可能已被 FSP 设置的值。

通过 JAXP 工厂或SAXParser指定的 JAXP 属性优先于系统属性,jaxp.properties文件以及FEATURE_SECURE_PROCESSING

使用限制

原文:docs.oracle.com/javase/tutorial/jaxp/limits/using.html

环境评估

评估包括在系统级别考虑应用程序可用的内存量,是否接受和处理来自不受信任来源的 XML、XSD 或 XSL 源,以及在应用程序级别考虑是否使用某些构造(如 DTD)。

内存设置和限制

XML 处理可能非常消耗内存。允许消耗的内存量取决于特定环境中应用程序的要求。必须防止处理格式不正确的 XML 数据消耗过多内存。

默认限制通常设置为允许大多数应用程序的合法 XML 输入,并允许小型硬件系统(如 PC)的内存使用。建议将限制设置为可能的最小值,以便在消耗大量内存之前捕获任何格式不正确的输入。

这些限制是相关的,但并非完全冗余。您应为所有限制设置适当的值:通常限制应设置为比默认值小得多的值。

例如,可以设置ENTITY_EXPANSION_LIMITGENERAL_ENTITY_SIZE_LIMIT来防止过多的实体引用。但是当扩展和实体大小的确切组合未知时,TOTAL_ENTITY_SIZE_LIMIT可以作为整体控制。同样,虽然TOTAL_ENTITY_SIZE_LIMIT控制替换文本的总大小,但如果文本是一个非常大的 XML 块,ENTITY_REPLACEMENT_LIMIT会限制文本中可以出现的节点总数,并防止系统过载。

通过使用getEntityCountInfo属性估计限制

为帮助您分析应设置的限制值,提供了一个名为http://www.oracle.com/xml/jaxp/properties/getEntityCountInfo的特殊属性。以下代码片段显示了使用该属性的示例:

parser.setProperty("http://www.oracle.com/xml/jaxp/properties/getEntityCountInfo", "yes");

查看示例以获取有关下载示例代码的更多信息。

当程序在 W3C MathML 3.0 中运行时,将打印出以下表格:

属性 限制 总大小 大小 实体名称
ENTITY_EXPANSION_LIMIT 64000 1417 0 null
MAX_OCCUR_NODE_LIMIT 5000 0 0 null
ELEMENT_ATTRIBUTE_LIMIT 10000 0 0 null
TOTAL_ENTITY_SIZE_LIMIT 50000000 55425 0 null
GENERAL_ENTITY_SIZE_LIMIT 0 0 0 null
PARAMETER_ENTITY_SIZE_LIMIT 1000000 0 7303 %MultiScriptExpression
MAX_ELEMENT_DEPTH_LIMIT 0 2 0 null
MAX_NAME_LIMIT 1000 13 13 null
ENTITY_REPLACEMENT_LIMIT 3000000 0 0 null

在此示例中,实体引用的总数,或实体扩展,为 1417;默认限制为 64000。所有实体的总大小为 55425;默认限制为 50000000。在解析所有引用后,最大的参数实体是 %MultiScriptExpression,长度为 7303;默认限制为 1000000。

如果这是应用程序预计要处理的最大文件,请建议将限制设置为较小的数字。例如,ENTITY_EXPANSION_LIMIT 设置为 2000,TOTAL_ENTITY_SIZE_LIMIT 设置为 100000,PARAMETER_ENTITY_SIZE_LIMIT 设置为 10000。

设置限制

限制可以像其他 JAXP 属性一样设置。它们可以通过工厂方法或解析器设置:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setAttribute(name, value);

SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
parser.setProperty(name, value);

XMLInputFactory xif = XMLInputFactory.newInstance();
xif.setProperty(name, value);

SchemaFactory schemaFactory = SchemaFactory.newInstance(schemaLanguage);
schemaFactory.setProperty(name, value);

TransformerFactory factory = TransformerFactory.newInstance();
factory.setAttribute(name, value);

以下示例显示了如何使用 DocumentBuilderFactory 设置限制:

dbf.setAttribute(JDK_ENTITY_EXPANSION_LIMIT, "2000");
dbf.setAttribute(TOTAL_ENTITY_SIZE_LIMIT, "100000");
dbf.setAttribute(PARAMETER_ENTITY_SIZE_LIMIT, "10000"); 
dbf.setAttribute(JDK_MAX_ELEMENT_DEPTH, "100"); 

使用系统属性

如果更改代码不可行,系统属性可能很有用。

要为整个 JDK 或 JRE 的调用设置限制,请在命令行上设置系统属性。要仅为应用程序的一部分设置限制,可以在该部分之前设置系统属性,并在之后清除。以下代码显示了如何使用系统属性:

public static final String SP_GENERAL_ENTITY_SIZE_LIMIT = "jdk.xml.maxGeneralEntitySizeLimit";

//set limits using system property
System.setProperty(SP_GENERAL_ENTITY_SIZE_LIMIT, "2000");

//this setting will affect all processing after it's set
...

//after it is done, clear the property
System.clearProperty(SP_GENERAL_ENTITY_SIZE_LIMIT);

请注意,属性的值应为整数。如果输入的值不包含可解析的整数,将抛出 NumberFormatException;请参阅方法 parseInt(String)

查看 示例 以获取有关下载示例代码的更多信息。

使用 jaxp.properties 文件

jaxp.properties 文件是一个配置文件。通常位于 ${*java.home*}/lib/jaxp.properties,其中 *java.home* 是 JRE 安装目录,例如,[安装目录路径]/jdk8/jre。

可通过向 jaxp.properties 文件添加以下行来设置限制:

jdk.xml.maxGeneralEntitySizeLimit=2000

请注意,属性名称与系统属性相同,并具有前缀 jdk.xml。属性的值应为整数。如果输入的值不包含可解析的整数,将抛出 NumberFormatException;请参阅方法 parseInt(String)

当在文件中设置属性时,所有 JDK 和 JRE 的调用都将遵守限制。

posted @ 2024-04-12 15:08  绝不原创的飞龙  阅读(33)  评论(0编辑  收藏  举报