Java技术和XML可以说是过去五年中最重要的编程开发。 结果,用于使用Java语言的XML的API激增了。 两种最受欢迎的文档对象模型(DOM)和XML的简单API(SAX)引起了极大的兴趣,紧随其后的是JDOM和数据绑定API(请参阅参考资料 )。 彻底了解这些API中的一两个是一项艰巨的任务。 正确使用所有这些使您成为专家。 但是,越来越多的Java开发人员发现他们不再需要广泛的SAX和DOM知识,这在很大程度上要归功于Sun Microsystems的JAXP工具包。 用于XML处理的Java API(JAXP)使即使是初次使用Java的程序员也可以管理XML,同时仍然为高级开发人员提供了很多便利。 就是说,即使是使用JAXP的高级开发人员也常常会对他们所依赖的API产生误解。
本文假定您具有SAX和DOM的一些基本知识。 如果您不熟悉XML解析,则可能想先通过在线资源阅读SAX和DOM,或者略读我的书(请参阅参考资料 )。 您无需精通回调或DOM Node
,但至少应了解SAX和DOM在解析API。 对它们之间的差异有一个基本的了解也将有所帮助。 掌握了这些基础知识之后,本文将变得更加有意义。
严格来说,JAXP是一个API,但更准确地说是一个抽象层 。 它没有提供解析XML的新方法,也没有添加到SAX或DOM中,也没有为Java和XML处理提供新功能。 (如果您现在不敢相信,那么您正在阅读正确的文章。)相反,JAXP使使用DOM和SAX来处理一些困难的任务变得更加容易。 它还使您可以以与供应商无关的方式处理使用DOM和SAX API时可能遇到的一些特定于供应商的任务。
没有SAX,DOM或其他XML解析API, 就无法解析XML 。 我已经看到许多要求将SAX,DOM,JDOM和dom4j与JAXP进行比较的请求,但是进行这样的比较是不可能的,因为前四个API的用途与JAXP完全不同。 SAX,DOM,JDOM和dom4j都解析XML。 JAXP提供了一种获取这些解析器及其所公开数据的方法,但没有提供解析XML文档的新方法。 如果要正确使用JAXP,理解这一区别至关重要。 它还很可能使您领先于许多其他XML开发人员。
如果您仍然不确定,请确保您拥有JAXP发行版(请参阅Going bigtime )。 启动Web浏览器并加载JAXP API文档。 导航到位于javax.xml.parsers
包中的API的解析部分。 令人惊讶的是,您只会找到六个班级。 这个API有多难? 所有这些类都位于现有解析器的顶部。 其中两个仅用于错误处理。 JAXP比人们想象的要简单得多。 那么,为什么所有的困惑呢?
Sun打包JAXP的方式和默认情况下JAXP使用的解析器导致了很多解析器/ API的混乱。 在早期版本的JAXP中,Sun包括JAXP API(我刚才提到的那六个类,还有一些用于转换) 和一个名为Crimson的解析器。 深红色是com.sun.xml
软件包的一部分。 在JDK中包含的更新版本的JAXP中,Sun重新包装了Apache Xerces解析器(请参阅参考资料 )。 但是,在这两种情况下,解析器都是JAXP发行版的一部分,而不是JAXP API的一部分。
这样考虑:JDOM随Apache Xerces解析器一起提供。 该解析器不是JDOM的一部分,而是由JDOM使用的,因此它被包括在内以确保JDOM可以立即使用。 相同的原则适用于JAXP,但并未明确宣传:JAXP带有一个解析器,因此可以立即使用。 但是,许多人将Sun的解析器中包含的类称为JAXP API本身的一部分。 例如,有关新闻组的一个常见问题曾经是:“如何使用JAXP附带的XMLDocument
类?它的目的是什么?” 答案有些复杂。
首先, com.sun.xml.tree.XMLDocument
类不是JAXP的一部分。 它是Sun的Crimson解析器的一部分,包装在JAXP的早期版本中。 因此,这个问题从一开始就令人误解。 其次,JAXP的主要目的是在与解析器打交道时提供供应商独立性。 使用JAXP,您可以将相同的代码与Sun的XML解析器,Apache的Xerces XML解析器和Oracle的XML解析器一起使用。 因此,使用特定于Sun的类违反了使用JAXP的要点。 您是否开始看到这个主题变得如何混乱? JAXP发行版中的解析器和API结合在一起,并且一些开发人员将类和功能误认为是另一类的一部分,反之亦然。
现在,您不仅可以看到所有混乱,还可以继续学习一些代码和概念。
SAX是一种用于处理XML的事件驱动方法。 它包含许多回调。 例如,每次SAX解析器遇到元素的开始标记时,就会调用startElement()
回调。 对字符数据调用characters()
回调,然后对元素的end标签调用endElement()
。 存在更多用于文档处理,错误和其他词法结构的回调。 你明白了。 SAX程序员实现定义这些回调的SAX接口之一。 SAX还提供了一个名为DefaultHandler
的类(在org.xml.sax.helpers
包中),该类实现所有这些回调,并提供所有回调方法的默认的空实现。 (您将在下一节“ 处理 DOM”中对DOM的讨论中看到这一点很重要。)SAX开发人员只需要扩展此类,然后实现需要插入特定逻辑的方法即可。 因此,SAX中的关键是为这些各种回调提供代码,然后让解析器在适当的时候触发每个回调。 这是典型的SAX例程:
SAXParser
实例。 DefaultHandler
的类)。 JAXP的SAX组件为完成所有这些操作提供了一种简单的方法。 如果没有JAXP,则必须直接从供应商类(例如org.apache.xerces.parsers.SAXParser
)实例化SAX解析器实例,或者它必须使用名为XMLReaderFactory
的SAX帮助器类(也在org.xml.sax.helpers
包)。 第一种方法的问题很明显:它不是供应商中立的。 第二个问题是工厂要求使用解析器类的String
名称作为参数(同样是Apache类org.apache.xerces.parsers.SAXParser
)。 您可以通过将另一个解析器类作为String
传入来更改解析器。 使用这种方法,如果您更改解析器名称,则无需更改任何导入语句,但仍然需要重新编译该类。 这显然不是最佳方案。 能够在不重新编译类的情况下更改解析器会容易得多。
JAXP提供了更好的替代方案:它使您可以将解析器作为Java系统属性来提供。 当然,从Sun下载发行版时,您将获得使用Sun的Xerces版本的JAXP实现。 更改解析器-比如,Oracle的解析器-要求更改类路径设置,从一个解析器实现移动到另一个,但它不需要重新编译代码。 这就是JAXP所具有的魔力(抽象)。
JAXP SAXParserFactory
类是能够轻松更改解析器实现的关键。 您必须创建该类的新实例(稍后我将介绍)。 创建新实例后,工厂将提供一种获取具有SAX功能的解析器的方法。 在幕后,JAXP实现会处理与供应商相关的代码,使您的代码愉快地不受污染。 该工厂还具有其他一些不错的功能。
除了创建SAX解析器实例的基本工作之外,工厂还允许您设置配置选项。 这些选项影响通过工厂获得的所有解析器实例。 在JAXP 1.3提供的两种最常用的选项是与一组命名空间意识setNamespaceAware(boolean awareness)
,并用打开DTD验证setValidating(boolean validating)
。 请记住,一旦设置了这些选项,它们将影响方法调用后从工厂获得的所有实例。
设置好工厂后,调用newSAXParser()
将返回JAXP SAXParser
类的现成实例。 此类包装基础的SAX解析器(SAX类org.xml.sax.XMLReader
的实例)。 它还可以保护您避免对解析器类使用任何特定于供应商的添加。 (还记得本文前面有关XmlDocument
类的讨论吗?)此类允许开始实际的解析行为。 清单1显示了如何创建,配置和使用SAX工厂:
SAXParserFactory
import java.io.OutputStreamWriter;
import java.io.Writer;
// JAXP
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
// SAX
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class TestSAXParsing {
public static void main(String[] args) {
try {
if (args.length != 1) {
System.err.println ("Usage: java TestSAXParsing [filename]");
System.exit (1);
}
// Get SAX Parser Factory
SAXParserFactory factory = SAXParserFactory.newInstance();
// Turn on validation, and turn off namespaces
factory.setValidating(true);
factory.setNamespaceAware(false);
SAXParser parser = factory.newSAXParser();
parser.parse(new File(args[0]), new MyHandler());
} catch (ParserConfigurationException e) {
System.out.println("The underlying parser does not support " +
" the requested features.");
} catch (FactoryConfigurationError e) {
System.out.println("Error occurred obtaining SAX Parser Factory.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyHandler extends DefaultHandler {
// SAX callback implementations from ContentHandler, ErrorHandler, etc.
}
在清单1中 ,您可以看到在使用工厂时可能会发生两个特定于JAXP的问题:无法获取或配置SAX工厂,以及无法配置SAX解析器。 由FactoryConfigurationError
表示的第一个问题通常在无法获取JAXP实现或系统属性中指定的解析器时发生。 当所使用的解析器中的请求的功能不可用时,将发生由ParserConfigurationException
表示的第二个问题。 两者都易于处理,并且在使用JAXP时不会造成任何困难。 实际上,您可能希望编写代码来尝试设置一些功能并优雅地处理某些功能不可用的情况。
获得工厂,关闭名称空间支持并打开验证后,将获得一个SAXParser
实例。 然后开始解析。 SAX解析器的parse()
方法采用我前面提到的SAX HandlerBase
帮助器类的实例,您的自定义处理程序类对此实例进行了扩展。 请参阅代码分发,以查看带有完整Java清单的此类的实现(请参见下载 )。 您还传递了要解析的File
。 但是, SAXParser
类包含的功能远不止单个方法。
一旦有了SAXParser
类的实例,就可以做很多事情,而不仅仅是将其传递给File
进行解析。 由于大型应用程序中组件的通信方式,因此并不总是安全地假设对象实例的创建者是其用户。 一个组件可能会创建SAXParser
实例,而另一个组件(可能由另一位开发人员编码)可能需要使用同一实例。 因此,JAXP提供了确定解析器设置的方法。 例如,您可以使用isValidating()
来确定解析器是否将执行验证,或者使用isNamespaceAware()
来查看解析器是否可以处理XML文档中的名称空间。 这些方法可以为您提供有关解析器可以执行的操作的信息,但是仅具有SAXParser
实例的用户(而不是SAXParserFactory
本身)无法更改这些功能。 您必须在解析器工厂级别执行此操作。
您还可以通过多种方法来请求文档的解析。 而不是仅仅接受File
和SAX DefaultHandler
情况下, SAXParser
的parse()
方法也可以接受SAX InputSource
,一个Java InputStream
或URL
以字符串形式,都带有DefaultHandler
实例。 因此,您仍然可以解析以各种形式包装的文档。
最后,您可以获取基础的SAX解析器( org.xml.sax.XMLReader
的实例),并通过SAXParser
的getXMLReader()
方法直接使用它。 获得此基础实例后,即可使用常规的SAX方法。 清单2显示了SAXParser
类(JAXP中用于SAX解析的核心类)的各种用法的示例:
SAXParser
类 // Get a SAX Parser instance
SAXParser saxParser = saxFactory.newSAXParser();
// Find out if validation is supported
boolean isValidating = saxParser.isValidating();
// Find out if namespaces are supported
boolean isNamespaceAware = saxParser.isNamespaceAware();
// Parse, in a variety of ways
// Use a file and a SAX DefaultHandler instance
saxParser.parse(new File(args[0]), myDefaultHandlerInstance);
// Use a SAX InputSource and a SAX DefaultHandler instance
saxParser.parse(mySaxInputSource, myDefaultHandlerInstance);
// Use an InputStream and a SAX DefaultHandler instance
saxParser.parse(myInputStream, myDefaultHandlerInstance);
// Use a URI and a SAX DefaultHandler instance
saxParser.parse("http://www.newInstance.com/xml/doc.xml",
myDefaultHandlerInstance);
// Get the underlying (wrapped) SAX parser
org.xml.sax.XMLReader parser = saxParser.getXMLReader();
// Use the underlying parser
parser.setContentHandler(myContentHandlerInstance);
parser.setErrorHandler(myErrorHandlerInstance);
parser.parse(new org.xml.sax.InputSource(args[0]));
到目前为止,我已经谈论了很多有关SAX的内容,但是我还没有发表任何引人注目的或令人惊讶的内容。 JAXP的新增功能很少,特别是在涉及SAX的地方。 这种最小的功能使您的代码更具可移植性,并允许其他开发人员通过任何与SAX兼容的XML解析器自由或商业地使用它。 而已。 将SAX与JAXP结合使用仅需更多。 如果您已经知道SAX,那么您的使用率大约为98%。 您只需要学习两个新类和几个Java异常,就可以开始学习了。 如果您从未使用过SAX,那么现在就很容易开始。
如果您认为需要休息一下以应对DOM的挑战,则可以节省一些时间。 在JAXP中使用DOM与在SAX中使用DOM几乎相同。 您要做的就是更改两个类名和一个返回类型,您就在那儿。 如果您了解SAX的工作原理以及DOM是什么,那么您将没有任何问题。
DOM和SAX之间的主要区别在于API本身的结构。 SAX由一组基于事件的回调组成,而DOM具有内存中的树结构。 使用SAX,永远不会有任何数据结构可以使用(除非开发人员手动创建一个)。 因此,SAX无法使您修改XML文档。 DOM确实提供了此功能。 org.w3c.dom.Document
类表示XML文档,并且由表示元素,属性和其他XML构造的DOM节点组成。 因此,JAXP无需触发SAX回调。 它仅负责从解析中返回DOM Document
对象。
有了对DOM的基本了解以及DOM和SAX之间的区别,您不需要了解更多。 清单3中的代码看起来与清单1中的SAX代码非常相似。 首先,获得DocumentBuilderFactory
(以清单1中的SAXParserFactory
相同的方式)。 然后将工厂配置为处理验证和名称空间(与在SAX中相同)。 接下来,从工厂中检索DocumentBuilder
实例(类似于SAXParser
实例)(以相同的方式SAXParser
您就知道了)。 然后可以进行解析,并且将得到的DOM Document
对象移交给打印DOM树的方法:
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
// JAXP
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
// DOM
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class TestDOMParsing {
public static void main(String[] args) {
try {
if (args.length != 1) {
System.err.println ("Usage: java TestDOMParsing " +
"[filename]");
System.exit (1);
}
// Get Document Builder Factory
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance();
// Turn on validation, and turn off namespaces
factory.setValidating(true);
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(args[0]));
// Print the document from the DOM tree and
// feed it an initial indentation of nothing
printNode(doc, "");
} catch (ParserConfigurationException e) {
System.out.println("The underlying parser does not " +
"support the requested features.");
} catch (FactoryConfigurationError e) {
System.out.println("Error occurred obtaining Document " +
"Builder Factory.");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void printNode(Node node, String indent) {
// print the DOM tree
}
}
此代码可能会引起两个问题(例如JAXP中的SAX): FactoryConfigurationError
和ParserConfigurationException
。 每种情况的原因都与SAX相同。 实现类中存在问题(导致FactoryConfigurationError
),或者提供的解析器不支持所请求的功能(导致ParserConfigurationException
)。 在这方面,DOM和SAX之间的唯一区别是,使用DOM可以将DocumentBuilderFactory
替换为SAXParserFactory
,将DocumentBuilder
替换为SAXParser
。 就这么简单。 (您可以查看完整的代码清单,其中包括用于打印DOM树的方法;请参阅Download 。)
一旦有了DOM工厂,就可以获取DocumentBuilder
实例。 DocumentBuilder
实例可用的方法与SAX实例可用的方法非常相似。 主要区别在于parse()
方法的变体未采用SAX DefaultHandler
类的实例。 相反,它们返回表示已解析的XML文档的DOM Document
实例。 唯一的不同是为类SAX功能提供了两种方法:
setErrorHandler()
,它采用SAX ErrorHandler
实现来处理解析中可能出现的问题 setEntityResolver()
,它采用SAX EntityResolver
实现来处理实体解析 清单4显示了这些方法的示例:
DocumentBuilder
类 // Get a DocumentBuilder instance
DocumentBuilder builder = builderFactory.newDocumentBuilder();
// Find out if validation is supported
boolean isValidating = builder.isValidating();
// Find out if namespaces are supported
boolean isNamespaceAware = builder.isNamespaceAware();
// Set a SAX ErrorHandler
builder.setErrorHandler(myErrorHandlerImpl);
// Set a SAX EntityResolver
builder.setEntityResolver(myEntityResolverImpl);
// Parse, in a variety of ways
// Use a file
Document doc = builder.parse(new File(args[0]));
// Use a SAX InputSource
Document doc = builder.parse(mySaxInputSource);
// Use an InputStream
Document doc = builder.parse(myInputStream, myDefaultHandlerInstance);
// Use a URI
Document doc = builder.parse("http://www.newInstance.com/xml/doc.xml");
如果您对DOM的本部分感到有些无聊,那么您并不孤单。 我发现写起来有点无聊,因为将您对SAX的了解应用于DOM非常简单。
在Java 5.0(和JAXP 1.3)中,JAXP引入了一种验证文档的新方法。 验证不是在SAX或DOM工厂上简单地使用setValidating()
方法,而是在新的javax.xml.validation
包内将验证分为几个类。 我将需要比本文更多的空间来详细说明验证的所有细微差别-包括W3C XML Schema,DTD,RELAX NG模式和其他约束模型-但是如果您已经有了一些约束,那么使用起来就非常容易新的验证模型,并确保您的文档与之匹配。
首先,将约束模型(大概是磁盘上的某个文件)转换为JAXP可以使用的格式。 将文件加载到Source
实例中。 (我将在第2部分中更详细地介绍Source
;现在,只知道它代表磁盘上某个位置的文档,DOM Document
或几乎所有其他内容。)然后,创建一个SchemaFactory
并使用SchemaFactory.newSchema(Source)
加载模式SchemaFactory.newSchema(Source)
,它返回一个新的Schema
对象。 最后,使用该Schema
对象,使用Schema.newValidator()
创建一个新的Validator
对象。 清单5应该使我刚才所说的一切更加清楚:
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(args[0]));
// Handle validation
SchemaFactory constraintFactory =
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Source constraints = new StreamSource(new File(args[1]));
Schema schema = constraintFactory.newSchema(constraints);
Validator validator = schema.newValidator();
// Validate the DOM tree
try {
validator.validate(new DOMSource(doc));
System.out.println("Document validates fine.");
} catch (org.xml.sax.SAXException e) {
System.out.println("Validation error: " + e.getMessage());
}
一旦掌握了它,这将非常简单。 自己输入此代码,或查看完整列表(请参阅下载 )。
更改JAXP工厂类使用的解析器很容易。 更改解析器实际上意味着更改解析器工厂,因为所有SAXParser
和DocumentBuilder
实例都来自这些工厂。 工厂确定要加载哪个解析器,因此必须更改工厂。 要更改SAXParserFactory
接口的实现,请设置Java系统属性javax.xml.parsers.SAXParserFactory
。 如果未定义此属性,则返回默认实现(无论供应商指定的解析器如何)。 相同的原则适用于您使用的DocumentBuilderFactory
实现。 在这种情况下,将查询javax.xml.parsers.DocumentBuilderFactory
系统属性。
阅读本文之后,您几乎已经了解了JAXP的全部范围:
要了解JAXP的解析和验证功能,您将花费很少的技巧。 使JAXP投入使用的最困难的部分是更改系统属性,通过工厂而不是解析器或构建器设置验证,并弄清JAXP 不是什么。 JAXP在两个流行的Java和XML API之上提供了一个有用的可插入性层。 它使您的代码供应商变得中立,并允许您从解析器更改为解析器,而无需重新编译解析代码。 因此,下载JAXP并开始吧! 第2部分将向您展示JAXP如何帮助您转换XML文档。
翻译自: https://www.ibm.com/developerworks/java/library/x-jaxp/index.html