Java xml 工具 JDOM 使用详解.

一, 什么是JDOM工具

在编程中, 我们往往需要一些配置数据,  这些值我们一般不会hardcode在代码中的. 而是写在配置文件.

在Java世界中, 我们通常会把配置变量写在xml文件中.

而xml 也不只是配置文件这么简单, 实际上, 我们可以把一些用户数据写在xml里.

这样, xml就如一个微型简便的数据库.

简单来讲, 我们可以将一个对象的所有属性(Attribute)写在xml里, 也可以通过读取xml里的数据构造1个对象出来.

Java里有若干个读写xml的工具, 它们分别是

DOM:  方便,  但是会将整个xml所有数据一次过读进内存.

SAX:   内存占用少, 使用起来稍稍复杂.

JDOM: 基于SAX编写的xml工具, 集合上面两个工具的优点.

本文主要介绍JDOM.

二, 安装JDOM工具.

JDOM实际上只是1个jar包, 而不是1个eclipse插件.

JDOM的最新版本是2.0.5 , 也就是JDOM2 了..

可以在如下网址中下载.

http://www.jdom.org/downloads/

下载得到1个zip包, 解压出 jdom-2.0.5.jar 文件.

把它写进CLASSPATH变量里就可以使用了.

三,XML各个关键字简单介绍

在正式开始介绍JDOM 用法时, 有必要简单讲下xml各个组成部分.

如下图:

上面是一个常见的XML图, 它实际上存储了两个Student对象的数据.

下面简单解释一下xml的常见名词:

3.1 头部信息

头部信息包含了xml的版本信息, 以及编码方法.

头部信息是每个xml文件都必须备有的.

3.2 Element.

我们可以把Element(元素) 看成xml的一个基本单元.

1个Element的常见写法如下:

<A>abc</A>, 有点类似与html,   Element的值被两个标签(Tag)包住.

3.3 name of Element.

Element标签里的值就算是这个Element的名字了,  这个值很重要, JDOM检索Element基本上是靠它来检索(而不是依照Value来检索)>

Name 是Element里的1个必备组成部分.

也就是每1个Element都必须有1个name啦

3.4 Value of Element.

被name标签(tag),包住的部分就是value了.

注意, value并不是Element的必备部分,  例如上图例子中,  Element "fox:Student"  就没有Value, 但是它有3个子Element.

3.5 Child

child就是子Element, 例如上图例子中, id, name, age 都是 Student的child,  而 Student 是 StudentRecords的Child.

注意, child只是一层的, 也就说 id, name,age 并不是 StudentRecords的child啊.

3.6 Children

1个Element的所有child的集合,  例如上图StudentRecords 有两个child,  而这两个child各自又有3个child(id, name, student),

3.7 Decsendant

1个Element 的后代, 子孙集合

相对于child来将, 这个概念就是多层的,

就如一个Tree结构, 所有子孙节点(node) 在Descendant的集合里面.

3.8 Root Element

就是根节点的意思啦. 上图例子中的StudentRecords就是1个Root Element.

在JDOM中,  1个xml文件只能有1个Root Element,  所有其他Element都在Root Element的 Descendant 集合中.

3.9 NameSpace

首先Namespace不是必须的.

有时为了区分同名的节点, 可以为一些节点加入另1个属性, 就是所谓的命名空间.

实际上, 命名空间的存在可以将xml里的节点依照命名空间分成若干个部分.

也就系讲, 令到xml中, 某些element是属于这个Namespace,  某些element是另1个Namespace.

而且Namespace可以任意加在任何1个Element里.

在JDOM的世界里,

Namespace是1个重要的filter条件.

也就是说, 如果1个Element有Namespace,   如果只靠这个Element的name是filter不出来的, 必须加上命名空间才能得到这个Element.

命名空间作为element的一个属性(Attribute)存在.

通常来讲, 命名空间可以写成如下格式:

xmlns:ggl="www.google.com"

其中, xmlns是1个关键字, 表示后面的是1个命名空间.

www.google.com是1个URL, 作为命名空间的URL了. 因为Internet上每个网址都是唯一的.

注意这个URL只是Element的一个标石.  而不是说xml会去访问这个网址.

在项目中, 通常我们会专门写1个网页关于这个xml的信息, 而把这个网页地址作为1个Namespace.

ggl 这个就是命名空间的Prefix, 可以理解成命名空间的1个简称.

例如上面例子中.

<ggl:Student xmlns:ggl="www.google.com"><ggl:id>1</ggl:id><ggl:name>Jack</ggl:name><ggl:age>14</ggl:age></ggl:Student>

有4个element

都是属于1个命名空间, 而URL的值只定义一次就够了,  后面的都可以用命名空间来代替.

上面的只是1个例子.

实际上我们不需要为xml的每1个element都加上命名空间, 只需要在关键的element加上就ok了.

四,利用JDOM 把对象数据写入1个新文件.

这个小节中, 会说明

1. 如何创建1个xml.

2. 如何把1个对象的数据写入xml.

4.1 Student类

首先你要有个对象是吧.. 我们就把1个student类写出来

它有3个成员id, name, 和 age

class Student{private int id;private String name;private int age;public Student(int id, String name, int age){this.setId(id);this.setName(name);this.setAge(age);}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString(){return "Student: " + this.getId() + ", " + this.getName() + ", " + this.getAge();}

4.2 客户端代码:

public class Testxml2 {public static void Create(){Student sd1 = new Student(1,"Jack",14);Student sd2 = new Student(2,"Mike",15);Student sd3 = new Student(3,"Paul",15);// new a xml fileDocument docJdom = new Document();//Namespace(not must, it's optional, to indicate elements with same nameNamespace np1 = Namespace.getNamespace("ggl", "www.google.com");Namespace np2 = Namespace.getNamespace("fox","www.firefox.com");// new a new root nodeElement eRoot = new Element("StudentRecords");docJdom.addContent(eRoot);//add sd1 nodeElement nodeSd1 = new Element("Student",np1);nodeSd1.setNamespace(np1);//idElement nodeId1 = new Element("id");nodeId1.addContent(""+sd1.getId());//nameElement nodeName1 = new Element("name");nodeName1.addContent(""+sd1.getName());//ageElement nodeAge1 = new Element("age");nodeAge1.addContent(""+sd1.getAge());nodeSd1.addContent(nodeId1);nodeSd1.addContent(nodeName1);nodeSd1.addContent(nodeAge1);eRoot.addContent(nodeSd1);//add sd2 nodeElement nodeSd2 = new Element("Student",np1);nodeSd2.setNamespace(np1);//idElement nodeId2 = new Element("id");nodeId2.addContent(""+sd2.getId());//nameElement nodeName2 = new Element("name");nodeName2.addContent(""+sd2.getName());//ageElement nodeAge2 = new Element("age");nodeAge2.addContent(""+sd2.getAge());nodeSd2.addContent(nodeId2);nodeSd2.addContent(nodeName2);nodeSd2.addContent(nodeAge2);eRoot.addContent(nodeSd2);//add sd3 nodeElement nodeSd3 = new Element("Student",np2);nodeSd3.setNamespace(np2);//idElement nodeId3 = new Element("id");nodeId3.addContent(""+sd3.getId());//nameElement nodeName3 = new Element("name");nodeName3.addContent(""+sd3.getName());//ageElement nodeAge3 = new Element("age");nodeAge3.addContent(""+sd3.getAge());nodeSd3.addContent(nodeId3);nodeSd3.addContent(nodeName3);nodeSd3.addContent(nodeAge3);eRoot.addContent(nodeSd3);		OutputXML(docJdom,"/home/gateman/Studies/JavaDesignPattern/xml/02.xml");System.out.println("Done!");}private static void OutputXML(Document dXml, String sFilenm){XMLOutputter xot = new XMLOutputter();xot.setFormat(Format.getPrettyFormat());try{FileWriter fwXML = new FileWriter(sFilenm);xot.output(dXml, fwXML);}catch(Exception e){e.printStackTrace();}}
}

代码其实不难看懂.

1. 首先创建3个student对象

2. 创建两个命名空间

3. 建立1个Document对象(JDOM中, Document指的是xml文件本身, 一个xml文件对应1个Document对象.

4. 建立1个Root Element对象.

5. 根据3个对象依次建立3个element, 然后把这个3个element添加到Root Element里, 作为它的子element.

6. 利用XMLOutputter把Document对象写出到硬盘. 注意这个方法是覆盖已存在文件, 并不是append到文件末尾.

生成的xml文件是这样的:

gateman@TPEOS xml $ cat 02.xml
<?xml version="1.0" encoding="UTF-8"?>
<StudentRecords><ggl:Student xmlns:ggl="www.google.com"><id>1</id><name>Jack</name><age>14</age></ggl:Student><ggl:Student xmlns:ggl="www.google.com"><id>2</id><name>Mike</name><age>15</age></ggl:Student><fox:Student xmlns:fox="www.firefox.com"><id>3</id><name>Paul</name><age>15</age></fox:Student>
</StudentRecords>

五,把写入xml的方法作为1个接口

的确, 上面的代码又长又臭, 而且重复代码实在太多,.

所以, 我们一般会把写入xml的动作放入类的代码中, 作为1个接口.

5.1 XMLable 接口:

import org.jdom2.Element;
import org.jdom2.Namespace;
public interface XMLable {public Element buildElement(Namespace np);
}

只需定义1个返回1个Element对象的方法.

5.2 Student 类:

让Student类去实现这个接口.

import org.jdom2.Element;
import org.jdom2.Namespace;public class Student implements XMLable {private int id;private String name;private int age;public Student(int id, String name, int age){this.setId(id);this.setName(name);this.setAge(age);}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString(){return "Student: " + this.getId() + ", " + this.getName() + ", " + this.getAge();}@Overridepublic Element buildElement(Namespace np) {Element node = new Element("Student",np);node.setNamespace(np);//idElement nodeId = new Element("id");nodeId.addContent(""+this.getId());//nameElement nodeName = new Element("name");nodeName.addContent(""+this.getName());//ageElement nodeAge = new Element("age");nodeAge.addContent(""+this.getAge());node.addContent(nodeId);node.addContent(nodeName);node.addContent(nodeAge);return node;}
}

5.3 XMLExport类

把Output方法封装1个类的静态函数:

import org.jdom2.Document;
import org.jdom2.output.XMLOutputter;
import org.jdom2.output.Format;
import java.io.FileWriter;public class XMLExport {public static void OutputXML(Document dXml, String sFilenm){XMLOutputter xot = new XMLOutputter();xot.setFormat(Format.getPrettyFormat());try{FileWriter fwXML = new FileWriter(sFilenm);xot.output(dXml, fwXML);}catch(Exception e){e.printStackTrace();}}
}

5.4 客户端代码:

这样一来, 客户端代码就相当简洁了:

		Student sd1 = new Student(1,"Jack",14);Student sd2 = new Student(2,"Mike",15);Student sd3 = new Student(3,"Paul",15);// new a xml fileDocument docJdom = new Document();//Namespace(not must, it's optional, to indicate elements with same nameNamespace np1 = Namespace.getNamespace("ggl", "www.google.com");Namespace np2 = Namespace.getNamespace("fox","www.firefox.com");// new a new root nodeElement eRoot = new Element("StudentRecords");docJdom.addContent(eRoot);eRoot.addContent(sd1.buildElement(np1));eRoot.addContent(sd2.buildElement(np1));eRoot.addContent(sd3.buildElement(np2));XMLExport.OutputXML(docJdom,"/home/gateman/Studies/JavaDesignPattern/xml/04.xml");

六, 根据条件从XML文件中获取Element对象.

首先讲一句,

JDOM中没有根据xml文件中某个Element的值(Value)返回其Element的方法. 不要把它当做sql数据库那么强大啦.

基本上常用filter方法有种.

1.是根据名字来filter.

2.是根据Namespace来filter.

上面的filter返回的都是1个集合. 也就是说你还需要遍历它们的子Element才能获得真正需要的Element.

而且当1个Element具有Namespace, 当屏它的Name是filter不出来的, 必须用Name和Namespace一起来filter.

一步一步来..

6.1 根据文件名获得Document对象.

这个是前提.

代码也很简单, 需要用到类SaxBuilder(也在JDOM包中)

	SAXBuilder sbd = new SAXBuilder();Document docJdom = null;File xmlfile = null;try{xmlfile = new File("/home/gateman/Studies/JavaDesignPattern/xml/02.xml");docJdom = sbd.build(xmlfile);}catch(Exception e){e.printStackTrace();}

6.2 获取Root Element

JDOM中Root Element只会有1个.

而JDOM也提供1个获取Root Element的方法

当然前提是你有1个Document对象啦.

Element eRoot = docJdom.getRootElement();

6.3 getChild()方法

这个方法返回1个Element中的第1个子Element.

注意, 不包括孙Element哦.

它有两个重载方法.

getChild(String name)  -> 返回第1个名字是name.而且没有Namespace的子Element,

getChild(String name,Namespace np) -> 返回第一个名字是name, Namespace是np的子Element.

6.4 getChildren()方法

返回1个Element的所有子Element的集合,  这个集合是1个java.util.List对象.

注意, 也不包括孙Element, 只有1层哦.

也有两个重载方法

getChildren(String name)  -> 返回所有名字是name, 而且没有Namespace的子Element的对象集合,

getChildren(String name,Namespace np) -> 返回第所有名字是name, Namespace是np的子Element的对象集合.

6.5 getDescendant()方法

这个就厉害了.  Descendant是后代的意思, 这个方法能返回

1个Element的所有子孙后代的Element集合. 注意这个集合是1个org.jom2.util.IteratorIterable<Element> 对象的集合

它也有1个重载方法:

getDescendant(Filter ft)  ->  根据条件ft(1个Filter对象) 来获得所有符合条件的后代Element集合. 下面的例子会用到这个方法

这个方法是真正上的Filter了, 因为它能遍历1个Element的所有子孙.

也就说如过用它来遍历RootElement, 就相当于遍历整个xml文件!

6.6 Filter 类

org.jdom2.Filter是Jdom2包里的一个抽象类.

最常用的子类是ElementFilter. 

它的对象通常用来作为getDescendant 方法的参数.

new ElementFilter(name, np) 代表构造1个用名字name和命名空间np组成的Filter对象

6.7 getParentElement()方法

获得1个非RootElement的的父Element.

注意1个Element只会有1个父Element啦.

6.8 获取上面xml文件中id为1的Element的例子代码.

艾呀这篇文章太冗长了.

上面的xml是这样的:

gateman@TPEOS xml $ cat 04.xml 
<?xml version="1.0" encoding="UTF-8"?>
<StudentRecords><ggl:Student xmlns:ggl="www.google.com"><id>1</id><name>Jack</name><age>14</age></ggl:Student><ggl:Student xmlns:ggl="www.google.com"><id>2</id><name>Mike</name><age>15</age></ggl:Student><fox:Student xmlns:fox="www.firefox.com"><id>3</id><name>Paul</name><age>15</age></fox:Student>
</StudentRecords>

它包含3个Student对象的数据

假如我想获得id等于2的StudentElement, 可以这样写:

还是先说几句

首先, id = 2中, 2是1个值, 上面说过了, JDOM是不支持用值来检索的.

id是1个名字, 可以用"id"来检索.  该xml中有3个名字是"id"的Element(分别属于3个Student Element),  它们都没有Namespace.

所以只需要

利用RootElement的  getDescendant("id")得到3个id Element的集合, 然后再去遍历它们的值..

代码如下:

		SAXBuilder sbd = new SAXBuilder();Document docJdom = null;File xmlfile = null;try{xmlfile = new File("/home/gateman/Studies/JavaDesignPattern/xml/02.xml");docJdom = sbd.build(xmlfile);}catch(Exception e){e.printStackTrace();}Element rootElement = docJdom.getRootElement();//all the element named "id" & without namespaceIteratorIterable<Element> elist = rootElement.getDescendants(new ElementFilter("id"));Iterator it = elist.iterator();Element eId2 = null;//loopwhile(it.hasNext()){Element em = (Element)(it.next());if (2 == new Integer(em.getChild("id").getValue()).intValue()){eId2 = em.getParentElement(); }}

上面的eId2就是想要得到的Element对象.

七, 把1个Element封装成1个类

例如上面我获得了id=2 的element, 如何把这个element封装成1个类?

下面是1个利用简单工厂模式的例子

把构造类方法写在1个产品里的方法里:

7.1 BuildXmlObj 接口

import org.jdom2.Element;
public interface BuildXmlObj {public Object buildObj(Element elm);
}

它具有1个根据Element构造1个对象的方法.

7.2 BuildXmlStudent 类

实现上面的接口

import org.jdom2.Element;public class BuildXmlStudent implements BuildXmlObj {@Overridepublic Object buildObj(Element em) {try{return new Student(new Integer(em.getChild("id").getValue()).intValue(),  em.getChild("name").getValue(),new Integer(em.getChild("age").getValue()).intValue()); }catch(Exception e){e.printStackTrace();return null;}}
}

7.3 工厂 类

public class FactoryXmlObj {public static BuildXmlObj getBuildXmlObj(String name){BuildXmlObj bxj = null;switch(name){case "Student" :bxj = new BuildXmlStudent();break;//..}return bxj;}}

7.4 客户段代码:

SAXBuilder sbd = new SAXBuilder();Document docJdom = null;File xmlfile = null;try{xmlfile = new File("/home/gateman/Studies/JavaDesignPattern/xml/04.xml");docJdom = sbd.build(xmlfile);}catch(Exception e){e.printStackTrace();}Element rootElement = docJdom.getRootElement();//all the element named "id" & without namespaceIteratorIterable<Element> elist = rootElement.getDescendants(new ElementFilter("id"));Iterator it = elist.iterator();Element eId2 = null;//loopwhile(it.hasNext()){Element em = (Element)(it.next());if (2 == new Integer(em.getValue()).intValue()){eId2 = em.getParentElement(); break;}}//packaging to a Student objStudent std = null;BuildXmlObj bxj = FactoryXmlObj.getBuildXmlObj("Student");std = (Student)(bxj.buildObj(eId2));System.out.println(std);

1g

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注