1. 引言

本文将对比Java中常用的XML库和API。

这是Java XML支持系列文章的第二篇,如果想深入了解Java中的XPath支持,可以参考上一篇文章

2. 概述

现在我们深入探讨XML处理支持,首先简单解释相关缩写术语:

Java XML支持包含多种API,各有优缺点:

  • SAX:基于事件的解析API,提供底层访问,内存效率高,比DOM更快(不加载整个文档树)。缺点是不支持XPath导航,虽然高效但使用复杂。

  • DOM:基于模型的解析器,将树形文档加载到内存,保留元素顺序,支持双向导航、读写操作和XML修改。使用简单但内存消耗大。

  • StAX:兼具DOM的易用性和SAX的效率,但缺少DOM的XML修改功能,且只支持单向导航。

  • JAXB:支持双向导航,比DOM高效,支持XML到Java类型转换和XML修改,但只能解析有效XML文档。

注意:JAXP项目已于2013年停止维护,基本不再使用。

3. XML示例

本节将展示流行实现方案,通过实际示例对比差异。所有示例使用如下XML结构:

<tutorials>
    <tutorial tutId="01" type="java">
        <title>Guava</title>
        <description>Introduction to Guava</description>
        <date>04/04/2016</date>
        <author>GuavaAuthor</author>
    </tutorial>
    ...
</tutorials>

4. DOM4J

首先看DOM4J的使用,需要添加最新版依赖

这是最流行的XML处理库之一,支持双向读取、创建和更新文档。兼容DOM、SAX、XPath和XLST(SAX通过JAXP支持)。

示例:按ID过滤选择元素

SAXReader reader = new SAXReader();
Document document = reader.read(file);
List<Node> elements = document.selectNodes("//*[@tutId='" + id + "']");
return elements.get(0);

SAXReader从SAX事件创建DOM4J树,获得org.dom4j.Document后直接传入XPath字符串即可查询。

修改文档并更新文件:

for (Node node : nodes) {
    Element element = (Element)node;
    Iterator<Element> iterator = element.elementIterator("title");
    while (iterator.hasNext()) {
        Element title =(Element)iterator.next();
        title.setText(title.getText() + " updated");
    }
}
XMLWriter writer = new XMLWriter(
  new FileWriter(new File("src/test/resources/example_updated.xml")));
writer.write(document);
writer.close();

通过elementIterator遍历节点非常简单,修改后用XMLWriter将DOM4J树写入文件流。

创建新文档:

Document document = DocumentHelper.createDocument();
Element root = document.addElement("XMLTutorials");
Element tutorialElement = root.addElement("tutorial").addAttribute("tutId", "01");
tutorialElement.addAttribute("type", "xml");
tutorialElement.addElement("title").addText("XML with Dom4J");
...
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter(
  new FileWriter(new File("src/test/resources/example_new.xml")), format);
writer.write(document);
writer.close();

DocumentHelper提供createDocument创建空文档,通过链式调用添加元素和属性,最后格式化输出。

5. JDOM

使用JDOM需添加依赖,其风格与DOM4J相似。

示例:获取根元素下的所有子元素

SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(this.getFile());
Element tutorials = doc.getRootElement();
List<Element> titles = tutorials.getChildren("tutorial");

通过XPath按属性查询:

SAXBuilder builder = new SAXBuilder();
Document document = (Document) builder.build(file);
String filter = "//*[@tutId='" + id + "']";
XPathFactory xFactory = XPathFactory.instance();
XPathExpression<Element> expr = xFactory.compile(filter, Filters.element());
List<Element> node = expr.evaluate(document);

SAXBuilder创建文档实例后,用JDOM2的XPathFactory编译XPath表达式进行查询。

6. StAX

StAX从Java 6起内置JDK,无需额外依赖。示例展示获取所有元素:

首先定义数据类:

public class Tutorial {
    private String tutId;
    private String type;
    private String title;
    private String description;
    private String date;
    private String author;
    
    // 标准getter/setter
}

解析代码:

List<Tutorial> tutorials = new ArrayList<>();
XMLInputFactory factory = XMLInputFactory.newInstance();
XMLEventReader eventReader = factory.createXMLEventReader(new FileReader(this.getFile()));
Tutorial current;
while (eventReader.hasNext()) {
    XMLEvent event = eventReader.nextEvent();
    switch (event.getEventType()) {
        case XMLStreamConstants.START_ELEMENT:
            StartElement startElement = event.asStartElement();
            String qName = startElement.getName().getLocalPart();
            ...
            break;
        case XMLStreamConstants.CHARACTERS:
            Characters characters = event.asCharacters();
            ...
            break;
        case XMLStreamConstants.END_ELEMENT:
            EndElement endElement = event.asEndElement();
            
            // 检查结束元素并关闭资源
            break;
    }
}

需要额外定义数据类存储结果,通过事件处理器单向遍历文档。可见即使简单查询也需要大量代码,这就是SAX类API的典型特点。

7. JAXB

JAXB内置JDK,无需依赖。通过注解绑定Java对象和XML,操作极其简单。

解析XML:

JAXBContext jaxbContext = JAXBContext.newInstance(Tutorials.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Tutorials tutorials = (Tutorials) jaxbUnmarshaller.unmarshal(this.getFile());

直接映射为Java对象,后续操作如同普通Java结构。

生成XML需要添加注解:

public class Tutorial {
    ...
    
    public String getTutId() {
        return tutId;
    }
  
    @XmlAttribute
    public void setTutId(String tutId) {
        this.tutId = tutId;
    }
    ...
    @XmlElement
    public void setTitle(String title) {
        this.title = title;
    }
    ...
}

@XmlRootElement
public class Tutorials {
    private List<Tutorial> tutorial;

    // 标准getter/setter,带@XmlElement注解
}

@XmlRootElement定义根节点,@XmlAttribute/@XmlElement区分属性和元素。

生成XML:

Tutorials tutorials = new Tutorials();
tutorials.setTutorial(new ArrayList<>());
Tutorial tut = new Tutorial();
tut.setTutId("01");
...
tutorials.getTutorial().add(tut);
JAXBContext jaxbContext = JAXBContext.newInstance(Tutorials.class);
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
jaxbMarshaller.marshal(tutorials, file);

对象到XML的映射是处理XML最简单的方式,没有之一。

8. XPath表达式支持

复杂XPath表达式可使用Jaxen库,它支持多种对象模型(DOM/XOM/DOM4J/JDOM)。

示例:

String expression = "/tutorials/tutorial";
XPath path = new DOMXPath(expression);
List result = path.selectNodes(xmlDocument);

需添加依赖

9. 结论

Java XML处理方案众多,选择取决于具体需求:

  • 简单场景:首选JAXB,开发效率最高
  • 大文件处理:SAX/StAX内存占用低
  • 需要修改:DOM4J/JDOM功能全面
  • 复杂查询:结合Jaxen增强XPath

踩坑提醒:避免使用已废弃的JAXP;处理超大文件时慎用DOM;JAXB要求严格符合XML Schema规范。


原始标题:XML Libraries Support | Baeldung