本课程专注于 XML 的流式 API(StAX),这是一种基于 Java 技术的流式、事件驱动、拉取解析的 API,用于读取和写入 XML 文档。StAX 使您能够创建快速、相对易于编程且具有轻量级内存占用的双向 XML 解析器。
StAX 项目由 BEA 主导,得到了 Sun Microsystems 的支持,JSR 173 规范于 2004 年 3 月通过了 Java 社区流程的最终批准投票。StAX API 的主要目标是通过公开一个简单的基于迭代器的 API,将“解析控制权交给程序员。这允许程序员请求下一个事件(拉取事件),并允许以过程化方式存储状态。” StAX 的创建是为了解决两种最常见解析 API,SAX 和 DOM,的限制。
一般来说,处理 XML 信息集有两种编程模型:流式处理和文档对象模型(DOM)。
DOM 模型涉及创建代表整个文档树和 XML 文档的完整信息集状态的内存对象。一旦在内存中,DOM 树可以自由导航和任意解析,因此为开发人员提供了最大的灵活性。然而,这种灵活性的代价是潜在的大内存占用和显著的处理器需求,因为整个文档的表示必须作为对象在内存中保持,以便在文档处理期间使用。在处理小型文档时,这可能不是问题,但随着文档大小的增加,内存和处理器需求可能会迅速升高。
流式处理是指一种编程模型,在应用程序运行时串行传输和解析 XML 信息集,通常是实时的,并且通常来自动态来源,其内容事先并不完全知晓。此外,基于流的解析器可以立即开始生成输出,并且信息集元素在使用后可以立即丢弃和进行垃圾回收。虽然提供了较小的内存占用、降低的处理器需求和在某些情况下更高的性能,但流处理的主要折衷是您只能在文档中的一个位置看到信息集状态。您基本上受限于文档的“纸板筒”视图,这意味着您需要在阅读 XML 文档之前知道要进行哪些处理。
在处理 XML 时,流式处理模型特别适用于应用程序具有严格的内存限制,比如在运行 Java 平台微版(Java ME 平台)的手机上,或者当应用程序需要同时处理多个请求时,比如在应用服务器上。实际上,可以说大多数 XML 业务逻辑都可以从流式处理中受益,并且不需要在内存中维护整个 DOM 树。
流拉取解析是一种编程模型,其中客户端应用程序在需要与 XML 信息集交互时调用 XML 解析库的方法,即客户端只有在明确请求时才会获取(拉取)XML 数据。
流推送解析是一种编程模型,其中 XML 解析器在遇到 XML 信息集中的元素时向客户端发送(推送)XML 数据,即使客户端此时还没有准备好使用它。
在处理 XML 流时,拉取解析相比于推送解析提供了几个优势:
StAX 规范定义了 API 的许多用例:
对所有这些用例的完整讨论超出了本课程的范围。请参考 StAX 规范以获取更多信息。
作为 JAXP 家族中的一个 API,StAX 可以与 SAX、TrAX 和 JDOM 等其他 API 进行比较。在后两者中,StAX 不像 TrAX 或 JDOM 那样强大或灵活,但也不需要太多内存或处理器负载才能发挥作用,并且在许多情况下,StAX 可以胜过基于 DOM 的 API。上面概述的相同论点,权衡 DOM 模型与流模型的成本/效益,在这里同样适用。
有鉴于此,最接近的比较可以在 StAX 和 SAX 之间进行,正是在这里 StAX 提供了许多情况下有益的功能;其中一些包括:
以下表格总结了 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 公开了用于 XML 文档的迭代式、基于事件的处理的方法。XML 文档被视为一系列经过过滤的事件,并且信息集状态可以以过程化方式存储。此外,与 SAX 不同,StAX API 是双向的,可以实现对 XML 文档的读取和写入。
StAX API 实际上是两个不同的 API 集:一个光标 API 和一个迭代器 API。这两个 API 集将在本课程的后面更详细地解释,但它们的主要特点如下所述。
如其名称所示,StAX 光标 API 表示一个光标,您可以使用它从头到尾遍历 XML 文档。这个光标一次只能指向一件事,并且总是向前移动,从不后退,通常一次移动一个信息集元素。
两个主要的光标接口是XMLStreamReader
和XMLStreamWriter
。XMLStreamReader
包括了从 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
上调用诸如getText
和getName
之类的方法,以获取当前光标位置的数据。XMLStreamWriter
提供了与StartElement
和EndElement
事件类型对应的方法;例如:
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 信息作为字符串返回,这减少了对象分配的需求。
StAX 迭代器 API 将 XML 文档流表示为一组离散的事件对象。这些事件由应用程序拉取,并由解析器按照它们在源 XML 文档中读取的顺序提供。
基本的迭代器接口称为XMLEvent
,并且为事件迭代器 API 表中列出的每种事件类型都有子接口。用于读取迭代器事件的主要解析器接口是XMLEventReader
,用于写入迭代器事件的主要接口是XMLEventWriter
。XMLEventReader
接口包含五种方法,其中最重要的是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 事件返回;例如,当命名空间作为 XQuery 或 XPath 表达式的结果返回时。 |
Namespace | 与属性一样,命名空间通常作为 StartElement 的一部分报告,但有时希望将命名空间作为独立的 Namespace 事件报告。 |
请注意,只有在处理的文档包含 DTD 时,才会创建 DTD
、EntityDeclaration
、EntityReference
、NotationDeclaration
和 ProcessingInstruction
事件。
作为事件迭代器 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 |
在这个例子中有几个重要的事项需要注意:
StartElement
都有一个对应的 EndElement
,即使是空元素也是如此。
Attribute
事件被视为次要事件,并且可以从其对应的 StartElement
事件中访问。
Attribute
事件类似,Namespace
事件被视为次要事件,但在事件流中出现两次,并且可以从它们对应的 StartElement
和 EndElement
中分别访问两次。
Character
事件,即使这些元素没有字符数据。同样,Character
事件可以跨事件分割。
javax.xml.namespace.NamespaceContext
接口暴露的命名空间堆栈可以通过命名空间前缀或 URI 访问。
此时合理地问一下,“我应该选择哪个 API?我应该创建 XMLStreamReader
还是 XMLEventReader
的实例?为什么会有两种类型的 API?”
StAX 规范的作者针对三种类型的开发者:
鉴于这些广泛的开发类别,StAX 的作者认为定义两个小型、高效的 API 比过载一个更大、必然更复杂的 API 更有用。
在选择游标和迭代器 API 之间之前,你应该注意一些你可以使用迭代器 API 而不能使用游标 API 的事项:
XMLEvent
子类创建的对象是不可变的,可以在数组、列表和映射中使用,并且可以在解析器继续处理后传递到你的应用程序中。
XMLEvent
的子类型,这些子类型可以是全新的信息项,也可以是现有项目的扩展,但具有额外的方法。
同样,在做出选择时,请记住一些一般性建议:
一般来说,StAX 程序员通过使用 XMLInputFactory
、XMLOutputFactory
和 XMLEventFactory
类来创建 XML 流读取器、写入器和事件。通过在工厂上设置属性来进行配置,可以通过在工厂上使用 setProperty
方法将特定于实现的设置传递给底层实现。类似地,可以使用 getProperty
工厂方法查询特定于实现的设置。
下面描述了 XMLInputFactory
、XMLOutputFactory
和 XMLEventFactory
类,然后讨论了资源分配、命名空间和属性管理、错误处理,最后使用游标和迭代器 API 读取和写入流。
StAX 工厂类。XMLInputFactory
、XMLOutputFactory
和 XMLEventFactory
,让您定义和配置 XML 流读取器、流写入器和事件类的实现实例。
XMLInputFactory
类允许您配置由工厂创建的 XML 流读取器处理器的实现实例。通过在类上调用 newInstance
方法来创建抽象类 XMLInputFactory
的新实例。然后使用静态方法 XMLInputFactory.newInstance
来创建新的工厂实例。
派生自 JAXP,XMLInputFactory.newInstance
方法通过以下查找过程确定要加载的特定 XMLInputFactory
实现类:
javax.xml.stream.XMLInputFactory
系统属性。
lib/xml.stream.properties
文件。
META-INF/services/javax.xml.stream.XMLInputFactory
文件确定类名。
XMLInputFactory
实例。
在获取适当的 XMLInputFactory
引用之后,应用程序可以使用工厂来配置和创建流实例。以下表格列出了 XMLInputFactory
支持的属性。详细列表请参阅 StAX 规范。
javax.xml.stream.XMLInputFactory
属性
属性 | 描述 |
---|---|
isValidating | 打开实现特定的验证。 |
isCoalescing | (必需) 要求处理器合并相邻的字符数据。 |
isNamespaceAware | 关闭命名空间支持。所有实现必须支持命名空间。对非命名空间感知文档的支持是可选的。 |
isReplacingEntityReferences | (必需) 要求处理器用其替换值替换内部实体引用,并将其报告为字符或描述实体的事件集。 |
isSupportingExternalEntities | (必需) 要求处理器解析外部解析实体。 |
reporter | (必需) 设置并获取XMLReporter接口的实现。 |
resolver | (必需) 设置并获取XMLResolver接口的实现。 |
allocator | (必需) 设置并获取XMLEventAllocator接口的实现。 |
通过在类上调用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
属性来实例化工厂。用于获取实例的算法与XMLInputFactory
和XMLOutputFactory
相同,但引用javax.xml.stream.XMLEventFactory
系统属性。
XMLEventFactory
没有默认属性。
StAX 规范处理资源解析、属性和命名空间,以及错误和异常,如下所述。
XMLResolver
接口提供了在 XML 处理期间解析资源的方法。应用程序在XMLInputFactory
上设置接口,然后该工厂实例创建的所有处理器都设置该接口。
属性由 StAX 处理器使用游标接口中的查找方法和字符串以及迭代器接口中的Attribute
和Namespace
事件报告。请注意,命名空间被视为属性,尽管在游标和迭代器 API 中,命名空间与属性分开报告。还要注意,命名空间处理对于 StAX 处理器是可选的。有关命名空间绑定和可选命名空间处理的完整信息,请参阅 StAX 规范。
所有致命错误都通过javax.xml.stream.XMLStreamException
接口报告。所有非致命错误和警告都使用javax.xml.stream.XMLReporter
接口报告。
正如在本课程前面所描述的,使用 StAX 处理器读取 XML 流的方式——更重要的是,您得到的内容——取决于您是使用 StAX 游标 API 还是事件迭代器 API,这两个部分描述了如何使用这两个 API 读取 XML 流。
StAX 游标 API 中的XMLStreamReader
接口只允许您以向前方向读取 XML 流或文档,每次只能读取信息集中的一个项目。以下方法可用于从流中提取数据或跳过不需要的事件:
XMLStreamReader
的实例在任何时候都有一个当前事件,其方法在其上操作。当您在流上创建一个XMLStreamReader
实例时,初始当前事件是START_DOCUMENT
状态。然后可以使用XMLStreamReader.next
方法来跳到流中的下一个事件。
XMLStreamReader.next
方法加载流中下一个事件的属性。然后,您可以通过调用XMLStreamReader.getLocalName
和XMLStreamReader.getText
方法来访问这些属性。
当XMLStreamReader
游标位于StartElement
事件上时,它读取事件的名称和任何属性,包括命名空间。可以使用索引值访问事件的所有属性,并且还可以通过命名空间 URI 和本地名称查找。但请注意,只有当前StartEvent
上声明的命名空间可用;之前声明的命名空间不会被保留,重新声明的命名空间也不会被移除。
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);
这个示例取自 StAX 规范,展示了如何实例化一个输入工厂,创建一个读取器,并遍历 XML 流的元素:
XMLInputFactory f = XMLInputFactory.newInstance();
XMLStreamReader r = f.createXMLStreamReader( ... );
while(r.hasNext()) {
r.next();
}
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
来获取当前命名空间上下文。
StAX 是一个双向 API,游标和事件迭代器 API 都有自己的一套接口用于写入 XML 流。与读取流的接口一样,写入器 API 对于游标和事件迭代器之间存在显著差异。以下部分描述了如何使用这些 API 之一来写入 XML 流。
StAX 游标 API 中的XMLStreamWriter
接口允许应用程序写回到 XML 流或创建全新的流。XMLStreamWriter 具有让您执行以下操作的方法:
请注意,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>
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
,直到在流中添加或遇到除Attribute
或Namespace
之外的事件。这意味着当您向流中添加Attribute
或Namespace
时,它会附加到当前的StartElement
事件。
您可以使用Characters
方法转义字符如&
、<
、>
和"
。
setPrefix(...)
方法可用于显式绑定输出时使用的前缀,而 getPrefix(...)
方法可用于获取当前前缀。请注意,默认情况下,XMLEventWriter
会将命名空间绑定添加到其内部命名空间映射中。前缀在绑定它们的事件对应的 EndElement
后会失效。
应用服务器 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.jar
和 javaee.jar
文件中,这两个文件位于 install_dir/lib/
目录中。
JAXP 参考实现中包含了 StAX 代码示例,位于 INSTALL_DIR/jaxp-
version/samples/stax
目录中,展示了 Sun 的流式 XML 解析器实现的工作原理。这些示例在 示例代码 中有描述。
在继续使用示例代码之前,有两个关于流式 XML 解析器的方面需要注意:
下面将讨论这些主题。
流式 XML 解析器中实现的 javax.xml.stream.XMLStreamReader
不报告 CDATA 事件。如果您有一个需要接收此类事件的应用程序,请配置 XMLInputFactory
来设置以下特定于实现的 report-cdata-event
属性:
XMLInputFactory factory = XMLInptuFactory.newInstance();
factory.setProperty("report-cdata-event", Boolean.TRUE);
大多数应用程序不需要知道工厂实现类名。对于大多数应用程序,只需将 javaee.jar
和 appserv-ws.jar
文件添加到类路径即可,因为这两个 jar 文件在 META-INF/services
目录下提供了各种流式 XML 解析器属性的工厂实现类名,例如 javax.xml.stream.XMLInputFactory
、javax.xml.stream.XMLOutputFactory
和 javax.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
本节逐步介绍了 JAXP 参考实现包中包含的示例 StAX 代码。本节中使用的所有示例目录均位于INSTALL_DIR/jaxp-
version/samples/stax
目录中。
本节涵盖的主题如下:
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。在此示例中,过滤器仅接受StartElement
和EndElement
事件,并过滤掉其余事件。
readnwrite
目录包含EventProducerConsumer.java
,演示了如何使用 StAX 生产者/消费者机制同时读取和写入 XML 流。
writer
目录包含CursorWriter.java
,演示如何使用XMLStreamWriter
以编程方式编写 XML 文件。
除了写入示例外,本节中的所有 StAX 示例均使用示例 XML 文档BookCatalog.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 & 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;
}
要编译和运行游标示例,在终端窗口中,转到INSTALL_DIR/jaxp-
version/samples/
目录,并输入以下内容:
javac stax/cursor/*.java
在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
对象。
第一步是创建一个新的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);
}
要编译和运行游标到事件示例,在终端窗口中,转到INSTALL_DIR/jaxp-
version/samples/
目录,并输入以下内容:
javac -classpath ../lib/jaxp-ri.jar stax/cursor2event/*.java
在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
属性和值在事件的开标签中返回。
要编译和运行事件示例,在终端窗口中,转到 INSTALL_DIR/jaxp-
version/samples/
目录并输入以下内容:
javac -classpath ../lib/jaxp-ri.jar stax/event/*.java
对 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 过滤应用程序不需要的事件。在这个示例中,解析器过滤掉除了 StartElement
和 EndElement
之外的所有事件。
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 事件解析讨论。
要编译和运行过滤器示例,在终端窗口中,转到 INSTALL_DIR/jaxp-
version/samples/
目录并输入以下内容:
javac -classpath ../lib/jaxp-ri.jar stax/filter/*.java
在 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
文件的内容。
要编译和运行读写示例,在终端窗口中,转到INSTALL_DIR/jaxp-
version/samples/
目录并输入以下内容:
javac -classpath ../lib/jaxp-ri.jar stax/readnwrite/*.java
在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 规范不要求添加这个前缀,但是当输出流的最终范围不明确时,这是一个良好的实践。
要编译和运行 Writer 示例,在终端窗口中,转到 INSTALL_DIR/jaxp-
version/samples/
目录,并输入以下内容:
javac -classpath \
../lib/jaxp-ri.jar stax/writer/*.java
运行 CursorWriter
示例,指定输出应写入的文件名。
java stax/writer/CursorWriter -f *output_file*
CursorWriter
将创建一个包含 返回输出 中显示的数据的相应名称的输出文件。
有关 StAX 的更多信息,请参见:
有关使用 StAX 的一些有用文章,请参见:
原文:
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
属性、import
和 include
元素引用 XML Schema 组件。示例:schemaLocation="http://www.example.com/schema/bar.xsd"
使用 import
或 include
元素合并样式表:语法:<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。
名称:http://javax.xml.XMLConstants/property/accessExternalDTD
定义:限制对外部 DTD、外部实体引用到指定协议的访问。
值:参见属性的值
默认值:all
,允许连接到所有协议。
系统属性:javax.xml.accessExternalDTD
名称:http://javax.xml.XMLConstants/property/accessExternalSchema
定义:限制对由schemaLocation
属性、Import 和 Include 元素设置的外部引用协议的访问。
值:参见属性的值
默认值:all
,允许连接到所有协议。
系统属性:javax.xml.accessExternalSchema
名称:http://javax.xml.XMLConstants/property/accessExternalStylesheet
定义:限制对由样式表处理指令、文档函数、Import 和 Include 元素设置的外部引用协议的访问。
值:参见属性的值
默认值:all
,允许连接到所有协议。
系统属性:javax.xml.accessExternalStylesheet
这些属性可以在jaxp.properties
中指定,以定义所有使用 Java Runtime 的应用程序的行为。格式为property-name=[value][,value]*
。例如:
javax.xml.accessExternalDTD=file,http
属性名称与系统属性相同:javax.xml.accessExternalDTD
、javax.xml.accessExternalSchema
和javax.xml.accessExternalStylesheet
。
所有属性的值格式相同。
值:由逗号分隔的协议列表。协议是 URI 的 scheme 部分,或者在 JAR 协议的情况下,由冒号分隔的"jar"加上 scheme 部分。协议定义为:
scheme = alpha *( alpha | digit | "+" | "-" | "." )
其中 alpha = a-z 和 A-Z。
以及 JAR 协议:
jar[:scheme]
协议不区分大小写。值中由Character.isSpaceChar
定义的任何空格将被忽略。协议的示例包括file
、http
、jar: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。
当通过调用SchemaFactory
的newSchema
方法显式创建模式时。
当不需要外部资源时。例如,以下功能/属性由参考实现支持,并可用于指示处理器不加载外部 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。这种行为不会启用新的限制。
原文:
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 被视为选择加入。
© 表中从左到右的顺序反映了覆盖顺序。例如,如果通过 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 确实为没有安全管理器运行的系统和应用程序提供了直接的保护。对于这类应用程序,可以考虑使用下面详细描述的新功能来进行限制。
当改变代码可行时,通过 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
是一个普通的配置文件。它位于${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.
原文:
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
欲了解更多信息,请参阅以下资源:
XML 处理有时可能是一个消耗大量内存的操作。应用程序,特别是那些接受来自不受信任来源的 XML、XSD 和 XSL 的应用程序,应该通过使用 JDK 提供的 JAXP 处理限制来防范过度的内存消耗。
开发人员应该评估他们应用程序的需求和运行环境,以确定系统配置的可接受限制,并相应地设置这些限制。与大小相关的限制可用于防止处理畸形的 XML 源时消耗大量内存,而EntityExpansionLimit
将允许应用程序在可接受水平下控制内存消耗。
在本教程中,您将了解这些限制,并学习如何正确使用它们。
以下列表描述了 JDK 支持的 JAXP XML 处理限制。这些限制可以通过工厂 API、系统属性和jaxp.properties
文件指定。
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/entityExpansionLimit |
定义 | 限制实体扩展的数量。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 64000 |
系统属性 | jdk.xml.entityExpansionLimit |
自从 | 7u45, 8 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/elementAttributeLimit |
定义 | 限制元素可以拥有的属性数量。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 10000 |
系统属性 | jdk.xml.elementAttributeLimit |
自从 | 7u45, 8 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/maxOccurLimit |
定义 | 限制在构建包含值不是"unbounded"的maxOccurs属性的 W3C XML Schema 的语法时可以创建的内容模型节点的数量。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 5000 |
系统属性 | jdk.xml.maxOccurLimit |
自从 | 7u45, 8 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/totalEntitySizeLimit |
定义 | 限制包含通用实体和参数实体的所有实体的总大小。大小是所有实体的聚合计算。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 5x10⁷ |
系统属性 | jdk.xml.totalEntitySizeLimit |
自从 | 7u45, 8 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/maxGeneralEntitySizeLimit |
定义 | 限制任何通用实体的最大大小。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 0 |
系统属性 | jdk.xml.maxGeneralEntitySizeLimit |
自从 | 7u45, 8 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/maxParameterEntitySizeLimit |
定义 | 限制任何参数实体的最大大小,包括嵌套多个参数实体的结果。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 1000000 |
系统属性 | jdk.xml.maxParameterEntitySizeLimit |
自 JDK 7u45, 8 起 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/entityReplacementLimit |
定义 | 限制所有实体引用中节点的总数。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 3000000 |
系统属性 | jdk.xml.entityReplacementLimit |
自 JDK 7u111, 8u101 起 |
属性 | 描述 |
---|---|
名称 | http://www.oracle.com/xml/jaxp/properties/maxElementDepth |
定义 | 限制最大元素深度。 |
值 | 一个正整数。小于或等于 0 的值表示没有限制。如果值不是整数,则会抛出NumericFormatException异常。 |
默认值 | 0 |
系统属性 | jdk.xml.maxElementDepth |
自 JDK 7u65, 8u11 起 |
属性 | 描述 |
---|---|
名称 | 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 |
可以在jaxp.properties
文件中指定系统属性,以定义 JDK 或 JRE 的所有调用的行为。格式为system-property-name=value
。例如:
jdk.xml.maxGeneralEntitySizeLimit=1024
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
。
评估包括在系统级别考虑应用程序可用的内存量,是否接受和处理来自不受信任来源的 XML、XSD 或 XSL 源,以及在应用程序级别考虑是否使用某些构造(如 DTD)。
XML 处理可能非常消耗内存。允许消耗的内存量取决于特定环境中应用程序的要求。必须防止处理格式不正确的 XML 数据消耗过多内存。
默认限制通常设置为允许大多数应用程序的合法 XML 输入,并允许小型硬件系统(如 PC)的内存使用。建议将限制设置为可能的最小值,以便在消耗大量内存之前捕获任何格式不正确的输入。
这些限制是相关的,但并非完全冗余。您应为所有限制设置适当的值:通常限制应设置为比默认值小得多的值。
例如,可以设置ENTITY_EXPANSION_LIMIT
和GENERAL_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 的调用都将遵守限制。