提示:本文修订于2023年7月13日

虽然在面试过程中,很少有考官提问Java DOM相关内容,但是掌握Java DOM操作却是很有必要,尤其是当你想做自己软件作品的时候,利用Xml实现配置信息是必不可少的环节。本文记录了我在做个人作品(分布式消息中间件)过程中的若干Java DOM经验总结,以备后续复习使用。

Java DOM API给我的总体感觉是:入门简单,但是深入很难。在Java的DOM API中,主要有五种类型的对象,如下所示:

  • Document:代表整个XML文档
  • Element:代表一个XML元素
  • Attr:代表一个元素的某个属性
  • Text:表示元素的文本内容
  • Comment:表示注释的内容

这些DOM对象都很容易理解,看似简单,实则并非如此,因为很多人不知道如何去选择,选择哪种类型的对象去操作Xml。这就是我上文提到的“入门简单,深入很难”的原因所在。

正因为DOM API给我们的东西很多,导致我们出现了选择困难,搞不清楚到底那种操作方式才是最合理的,但实际上我们只需要极少东西就可以实现自己的目的。我通过经验发现只需要两类对象就可以实现基本的Xml操作:DocumentElement。关于DocumentElement使用介绍可以见下文。当然,要想达到这种境界,需要深入研究Java Dom源码才能领悟。幸好,本文中也带有源码级的解读,方便参考。

1、Document的使用介绍

Document必不可少,因为它是Xml文件的入口。Document用于文档的初始化,主要分四步:

第一步:创建一个DocumentBuilderFactory对象。
第二步:通过DocumentBuilderFactory对象创建一个DocumentBuilder对象。
第三步:通过DocumentBuilder对象的parse()方法解析XML文档,得到一个Document对象。
第四步:通过Document对象获取XML文档中的元素、属性、文本节点等。

以上四步对应的代码如下所示:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse("src/main/resources/dom2.xml");
NodeList nodes = doc.getChildNodes();

Document自己独有的方法有两个:

public NodeList getElementsByTagName(String tagname);

public Element getElementById(String elementId);

仔细品读这两个方法,你会发现它们设计的非常有意思:

(1)getElementsByTagName方法得到的是NodeList,而Node是所有对象(Document、Element、Attr、Text、Comment)的父类,此处是抽象化思维的极致体现,函数返回Node类这样的设计思想十分合理!

(2)getElementById方法得到的是Element对象,而非Node对象。不同于上面的极致抽象思想,因为Node对象也包括了Attr、Text、Comment这类对象,而像Attr、Text、Comment这类对象,自身压根就没有ID属性,不可能通过ID来获取,所以返回Node对象则有点不精准的感觉。另外,从这个函数的设计也可以看出来,在所有Node类的派生类中,Element类独树一帜,是个非常重要的东西。

2、Element的使用介绍

Element自身的方法有两个:

public String getAttribute(String name);

public NodeList getElementsByTagName(String name);

上面的两个方法分别用于获取自身属性和子元素。因为Element继承自Node类,所以还可以使用Node类的一个重要方法:

备注:Node类有两个非常容易引发误解的函数:getNodeValue和getTextContent,见下文介绍。

public String getTextContent()

因为Element具备了上述三个方法,所以可以大杀四方,通吃一切,以下面的代码为例:

<element attr="attr">text<subelement></subelement></element>

我们完全可以只使用Element实现所有的操作:

(1)使用element.getAttribute("attr")就可以获得属性的值

(2)使用element.getTextContent()就可以获得文本text的值

(3)使用element.getElementsByTagName("subelement")获得subelement元素

3、Node的误区提示

Node是所有对象(Document、Element、Attr、Text、Comment)的父类,在它里面有两个函数非常容易引发误解:

public String getNodeValue();

public String getTextContent()

还是以上面代码为例:

<element attr="attr">text<subelement></subelement></element>

element这个Node是没有nodeValue的,"text"不是它的nodeValue,而是个单独的Node。我们只能通过getTextContent获得element这个Node的文本内容。

4、经验总结

当我深入学习和使用Java DOM操作之后,越发感觉到Java设计者在处理Xml文档方面的繁琐。同时,也理解了为什么Jdom和dom4J能后来者居上。在Java的DOM API中有很多东西,但实际上我们只需要极少东西就可以实现操作,导致我们出现了选择困难,搞不清楚到底那种操作方式才是最合理的,其实我们只需要牢记Document和Element就可以化解这个问题。

5、Java DOM 源码解读

5.1、Node接口

Node是DOM的基础,它表示文档树中的某个节点。所有的节点对象都起源于Node接口,并派生为不同的类型:Document、Element、Attr、Text、Comment。Node的作用主要有三个:

(1)不用向下转型成具体的派生接口就可以获得nodeName、nodeValue等基本信息。

(2)实现DOM的遍历:

public Node getParentNode();
public NodeList getChildNodes();
public Node getFirstChild();
public Node getLastChild();    
public Node getPreviousSibling();    
public Node getNextSibling();

(3)获取节点的属性,仅限于Element节点:

public NamedNodeMap getAttributes();

备注:NamedNodeMap本质是个工具类,它并非派生自Node接口,顾名思义,便可知它是一个存放Node的Map容器。需要注意的是,它只在Node接口中使用了一次,只用于获取属性的方法中,所以可以知道这个容器只存放Attr派生类。

5.2、Document接口

见上文介绍。

5.3、Element接口

见上文介绍。

5.4、Attr接口

Attr接口派生自Node接口,即:

interface Attr extends Node

Attr接口的常用方法有两个,如下所示:

public String getName();
    
public String getValue();

5.5、CharacterData接口

CharacterData接口派生自Node,并且是Text接口和Comment接口的父类,如下所示:

interface Text extends CharacterData
interface Comment extends CharacterData

Text接口和Comment接口在编程中遇到的机会很少,所以不再赘述。

5.6、NodeList接口,就是一个工具类而已。

public interface NodeList {
    public Node item(int index);
    public int getLength();
}

6、Java DOM 实战解读

对于Java DOM 实战操练,我是从两个角度进行解读。第一个角度是nodeValue,第二个角度是textContent。

对于下面的xml节点来说:

<user id='000'>Tom</user>

它有两个node,分别是user和Tom。user这个node只有name,没有value,而Tom这个node正好相反。这是从nodeValue角度看到的情况,而从textContent角度,则只需要关注user这个node即可,它的文本内容是“Tom”,不需要关注Tom这个node。

书归正传,继续我们的实战操作,此番实战对象为下面的xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<users>
    <user id="1">
        <firstname>Peter</firstname>
        <lastname>Brown</lastname>
        <occupation>programmer</occupation>
    </user>
    <user id="2">
        <firstname>Martin</firstname>
        <lastname>Smith</lastname>
        <occupation>accountant</occupation>
    </user>
    <user id="3">
        <firstname>Lucy</firstname>
        <lastname>Gordon</lastname>
        <occupation>teacher</occupation>
    </user>    
</users>

6.1、通过nodeValue的形式解析文件。

需要明白一点:对于任意节点而言,有nodeName则无nodeValue,反之亦然,两者不可得兼。

import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class Dom1 {

    public static void main(String[] args) throws SAXException, IOException, ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse("src/main/resources/dom2.xml");
        NodeList nodes = doc.getChildNodes();
        readXml(nodes);

    }
    
    public static void readXml(NodeList nodes) {

        for (int i = 0; i < nodes.getLength(); i++) {

            if (nodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
                
                System.out.println("该节点的名称为:" + nodes.item(i).getNodeName());

                String value = nodes.item(i).getFirstChild().getNodeValue();

                if (value != null && !value.trim().equals("")) {

                    System.out.println("该节点的值为:" + value);

                }

                String attrValue = ((Element) (nodes.item(i))).getAttribute("id");

                if (attrValue != null && !attrValue.equals("")) {
                    System.out.println("该节点的id为:" + attrValue);
                }

                System.out.println("");

            }

            if (nodes.item(i).getChildNodes().getLength() != 0) {
                readXml(nodes.item(i).getChildNodes());
            }
        }
    }

}

6.2、通过textContent的形式解析文件。

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class Dom2 {
    public static void main(String argv[]) throws SAXException, IOException, ParserConfigurationException {
        File xmlFile = new File("src/main/resources/dom2.xml");
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = factory.newDocumentBuilder();
        Document doc = dBuilder.parse(xmlFile);
        System.out.println("Root element: " + doc.getDocumentElement().getNodeName());
        NodeList nList = doc.getElementsByTagName("user");

        for (int i = 0; i < nList.getLength(); i++) {
            Node node = nList.item(i);
            System.out.println("\nCurrent Element: " + node.getNodeName());
            if (node.getNodeType() == Node.ELEMENT_NODE) {
                Element elem = (Element) node;
                String uid = elem.getAttribute("id");
                Node node1 = elem.getElementsByTagName("firstname").item(0);
                String fname = node1.getTextContent();
                Node node2 = elem.getElementsByTagName("lastname").item(0);
                String lname = node2.getTextContent();
                Node node3 = elem.getElementsByTagName("occupation").item(0);
                String occup = node3.getTextContent();
                System.out.printf("User id: %s%n", uid);
                System.out.printf("First name: %s%n", fname);
                System.out.printf("Last name: %s%n", lname);
                System.out.printf("Occupation: %s%n", occup);
            }
        }
    }
}

6、Java DOM 小结

虽然Java DOM API给我们的东西很多,导致我们出现了选择困难,但实际上我们只需要牢记Document和Element就可以化解这个问题。纸上得来终觉浅,须知此事要躬行,通过不断的练习和体会,我们才能发现Java DOM API设计者的良苦用心。

7、扩展阅读

1、JDOM的详细介绍

标签: none

添加新评论