Spring Boot 使用 Dom4j XStream 操作 Xml


Xml 現在仍然占據着比較重要的地位,比如微信接口中使用了 Xml 進行消息的定義。本章重點討論 Xml 的新建、編輯、查找、轉化,可以這么理解,本章是使用了 dom4j、xstream 也是在開發者中使用最為廣泛的 。 本章主要是位大家提供一個操作 Xml 的類庫。

0 Dom4j XStream 簡單介紹

一句話 Dom4j 專注於 Xml 操作的高性能庫,Xstream 則專注於 對象之間的轉換。

Dom4j

Dom4j 為了支持 XPath、XML Schema、基於事件處理大文檔或流文檔。

Dom4j 為提供構建文檔表示的選項,為可通過 Dom4j-API 和標准底層 dom-API 並行訪問功能。

為實現上述宏偉目標,Dom4j 使用接口和抽象基本類方法並大量使用 JDK 中 Collections 類。

所以 Dom4j 有豐富的 API,在靈活性上面 Dom4j 更占有優勢,性能方面也無可挑剔。

聲名在外的 Sun-JAXM,大名鼎鼎的 Hibernate 中XML 配置文件解析都使用的是 Dom4j。

XStream

XStream 為基於注解不需要其它輔助類或映射文件 的OXMapping 技術,如果你用過 hibernate 或 mybatis 之類的 ORM 框架就不難理解這里的 OXM。

XStream 可以將 JavaBean 序列化為 XML,或將 XML 反序列化為 JavaBean,使得XML序列化不再繁瑣。

XStream 也可以將 JavaBean 序列化成 Json 或反序列化,使用非常方便。

沒有映射文件而且底層使用 xmlpull 推模型解析 XML,高性能、低內存占用,結合簡潔明了的 API,上手基本是分分鍾的事情。

XStream 同時也可以定制轉換類型策略並配有詳細的錯誤診斷,能讓你快速定位問題。

1 新建 Spring Boot Maven 示例工程項目

注意:是用來 IDEA 開發工具

  1. File > New > Project,如下圖選擇 Spring Initializr 然后點擊 【Next】下一步
  2. 填寫 GroupId(包名)、Artifact(項目名) 即可。點擊 下一步
    groupId=com.fishpro
    artifactId=xmldom4j
  3. 選擇依賴 Spring Web Starter 前面打鈎。
  4. 項目名設置為 spring-boot-study-xmldom4j.

2 引入依賴 Pom

  • dom4j-1.6.1 支持 Java 1.4+
  • dom4j-2.0.2 支持 Java 5+
  • dom4j-2.1.1 支持 Java 8+

mvnrepository 只有 1.6.1 那就用1.6.1

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/jaxen/jaxen xpath解析的時候需要引入 jaxen包 否則會報錯 java.lang.NoClassDefFoundError: org/jaxen/JaxenException-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream 支持xml轉bean -->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.11.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

3 Dom4j 代碼實例

3.1 打開一個遠程 xml

 /**
     * 解析遠程 XML 文件
     * @return Document xml 文檔
     * */
    public static Document parse(URL url) throws DocumentException{
        SAXReader reader = new SAXReader();
        Document document =reader.read(url);
        return document;
    }

3.2 創建一個 xml 文檔

/**
     * 創建一個 xml 文檔
     * */
    public static Document createDocument() {
        Document document = DocumentHelper.createDocument();
        Element root = document.addElement("root");

        Element author1 = root.addElement("author")
                .addAttribute("name", "James")
                .addAttribute("location", "UK")
                .addText("James Strachan");

        Element author2 = root.addElement("author")
                .addAttribute("name", "Bob")
                .addAttribute("location", "US")
                .addText("Bob McWhirter");

        return document;
    }

3.3 遍歷

 System.out.println("====遍歷================================");
            //獲取根節點
            Element root = document.getRootElement();

            // 遍歷根節點下的子節點
            for (Iterator<Element> it = root.elementIterator(); it.hasNext();) {
                Element element = it.next();
                // do something
                System.out.println("根節下子節點名稱:"+element.getName());
            }
            // 遍歷子節點 author 下的子節點
            for (Iterator<Element> it = root.elementIterator("feed"); it.hasNext();) {
                Element foo = it.next();
                // do something
                System.out.println("author節點下節點名稱:"+foo.getName());
            }
            // 后去根節點的屬性
            for (Iterator<Attribute> it = root.attributeIterator(); it.hasNext();) {
                Attribute attribute = it.next();
                // do something
                System.out.println("根節下子節點屬性:"+attribute.getName());
            }

3.4 使用 xpath 獲取節點

System.out.println("================================================");
            //使用 XPath 獲取節點 獲取多個節點
            List<Node> list = document.selectNodes("//feed/entry");
            for (Node node :list
                 ) {

                System.out.println("list node:"+node.getName());
            }
            Node node = document.selectSingleNode("//feed");

            System.out.println("node:"+node.getName());

3.5 保存到 文件

//寫到文件里面
            Document document1=Dom4jUtils.createDocument();
            FileWriter out = new FileWriter("foo.xml");
            document1.write(out);
            out.close();

3.6 XML 文件轉文本

 Document xd=DocumentHelper.parseText(text);

3.7 文本轉 XML 文檔對象

 String text = "<person> <name>James</name> </person>";
Document document = DocumentHelper.parseText(text);

3.8 使用 XSLT 轉換 XML

/**
     * 使用XSLT轉換XML
     * */
    public static Document styleDocument(Document document, String stylesheet) throws Exception {

        TransformerFactory factory = TransformerFactory.newInstance();//實例化轉換器工廠 TransformerFactory
        Transformer transformer = factory.newTransformer(new StreamSource(stylesheet));// 創建一個轉化格式對象

        DocumentSource source = new DocumentSource(document); // XML 源對象
        DocumentResult result = new DocumentResult(); //轉換結果對象
        transformer.transform(source, result);//轉換操作

        Document transformedDoc = result.getDocument();//獲取轉換后的文檔
        return transformedDoc;
    }

4 XStream 代碼實例

4.1 不使用別名直接輸出 xml

Blog.java (路徑 src/main/java/com/fishpro/xmldom4j/domain/Blog.java)

public class Blog {
    private Author writer;
    private List entries = new ArrayList();

    public Blog(Author writer) {
        this.writer = writer;
    }

    public void add(Entry entry) {
        entries.add(entry);
    }

    public List getContent() {
        return entries;
    }
}

部分代碼

Blog teamBlog = new Blog(new Author("Guilherme Silveira"));
        teamBlog.add(new Entry("first","My first blog entry."));
        teamBlog.add(new Entry("tutorial",
                "Today we have developed a nice alias tutorial. Tell your friends! NOW!"));
        //1.如果不設置別名呢
        System.out.println(xstream.toXML(teamBlog));
        /** 1.不設置別名輸出
         * <com.fishpro.xmldom4j.domain.Blog>
         *   <writer>
         *     <name>Guilherme Silveira</name>
         *   </writer>
         *   <entries>
         *     <com.fishpro.xmldom4j.domain.Entry>
         *       <title>first</title>
         *       <description>My first blog entry.</description>
         *     </com.fishpro.xmldom4j.domain.Entry>
         *     <com.fishpro.xmldom4j.domain.Entry>
         *       <title>tutorial</title>
         *       <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
         *     </com.fishpro.xmldom4j.domain.Entry>
         *   </entries>
         * </com.fishpro.xmldom4j.domain.Blog>
         * */

4.2 使用別名直接輸出 xml

注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果

 //2.設置別名
        xstream.alias("blog", Blog.class);
        xstream.alias("entry", Entry.class);
        System.out.println(xstream.toXML(teamBlog));

        /** 2.設置別名
         * <blog>
         *   <writer>
         *     <name>Guilherme Silveira</name>
         *   </writer>
         *   <entries>
         *     <entry>
         *       <title>first</title>
         *       <description>My first blog entry.</description>
         *     </entry>
         *     <entry>
         *       <title>tutorial</title>
         *       <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
         *     </entry>
         *   </entries>
         * </blog>
         * */

4.3 替換節點名稱

注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果

 //3.writer 節點 轉為 author節點
        System.out.println("2.writer 節點 轉為 author節點");
        xstream.useAttributeFor(Blog.class, "writer");
        xstream.aliasField("author", Blog.class, "writer");
        System.out.println(xstream.toXML(teamBlog));

        xstream.addImplicitCollection(Blog.class, "entries");
        System.out.println(xstream.toXML(teamBlog));

        /** 3. author 替代了 write
         * <blog>
         *   <author>
         *     <name>Guilherme Silveira</name>
         *   </author>
         *   <entry>
         *     <title>first</title>
         *     <description>My first blog entry.</description>
         *   </entry>
         *   <entry>
         *     <title>tutorial</title>
         *     <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
         *   </entry>
         * </blog>
         * */

4.4 實體對象屬性作為節點的屬性

注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果

//4.writer 作為 blog 的屬性
        System.out.println("4.作為blog的屬性");
        xstream.useAttributeFor(Blog.class, "writer");
        xstream.registerConverter(new AuthorConverter());//作為blog的屬性
        System.out.println(xstream.toXML(teamBlog));
        /** 4.writer 作為 blog 的屬性
         * <blog author="Guilherme Silveira">
         *   <entry>
         *     <title>first</title>
         *     <description>My first blog entry.</description>
         *   </entry>
         *   <entry>
         *     <title>tutorial</title>
         *     <description>Today we have developed a nice alias tutorial. Tell your friends! NOW!</description>
         *   </entry>
         * </blog>
         * */

4.5 使用注解 @XStreamAlias

@XStreamAlias 可以應用到類似,也可以應用到實體對象 Bean 的屬性名上

本示例中使用了 User 和 Address 實體,他們的關系是 User 可以擁有多個 Address

User (路徑 src/main/java/com/fishpro/xmldom4j/domain/User.java)

@XStreamAlias("user")
public class User {
    @XStreamAlias("id")
    private Integer userId;

    @XStreamAlias("username")
    private String username;

    @XStreamImplicit
    private List<Address> addresses;

    public User(Integer userId,String username){
        this.userId=userId;
        this.username=username;
    }
    public User(Integer userId,String username,List<Address> addresses){
        this.userId=userId;
        this.username=username;
        this.addresses=addresses;
    }
    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }
}

Address (路徑 src/main/java/com/fishpro/xmldom4j/domain/Address.java)

@XStreamAlias("address")
public class Address {
    private String street;
    private String zipcode;
    private String mobile;

    public Address(String street,String zipcode,String mobile){
        this.street=street;
        this.zipcode=zipcode;
        this.mobile=mobile;
    }
    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
}
System.out.println("5.使用注解");
        //5.使用注解
        xstream.processAnnotations(User.class);
        User msg = new User(1, "fishpro");
        System.out.println(xstream.toXML(msg));
        /** 使用注解 @XStreamAlias("user")
         * <user>
         *   <id>1</id>
         *   <username>fishpro</username>
         * </user>
         * */

4.6 使用注解 @XStreamImplicit

注意使用了上面代碼的 Blog teamBlog 的定義。注釋部分為輸出效果

//6.使用注解 子節點是列表
        List<Address> addressList=new ArrayList<>();
        addressList.add(new Address("江蘇省南京市玄武大道1000號","201001","1801989098"));
        addressList.add(new Address("江蘇省南京市玄武大道1001號","201001","1811989098"));
        msg = new User(1, "fishpro",addressList);
        System.out.println(xstream.toXML(msg));
        /** 6.使用注解 子節點是列表
         * <user>
         *   <id>1</id>
         *   <username>fishpro</username>
         *   <address>
         *     <street>江蘇省南京市玄武大道1000號</street>
         *     <zipcode>201001</zipcode>
         *     <mobile>1801989098</mobile>
         *   </address>
         *   <address>
         *     <street>江蘇省南京市玄武大道1001號</street>
         *     <zipcode>201001</zipcode>
         *     <mobile>1811989098</mobile>
         *   </address>
         * </user>
         * */

4.9 屬性轉換器

當我們遇到日期的是,可能需要轉換成想要的格式,我們給 User 對象增加 created 屬性

    private Calendar created = new GregorianCalendar();

那么上面的示例就會輸出,注意以下多出了created節點

<user>
  <id>1</id>
  <username>fishpro</username>
  <address>
    <street>江蘇省南京市玄武大道1000號</street>
    <zipcode>201001</zipcode>
    <mobile>1801989098</mobile>
  </address>
  <address>
    <street>江蘇省南京市玄武大道1001號</street>
    <zipcode>201001</zipcode>
    <mobile>1811989098</mobile>
  </address>
  <created>
    <time>1565691626712</time>
    <timezone>Asia/Shanghai</timezone>
  </created>
</user>

接下來我們新建一個轉換器類
SingleValueCalendarConverter (路徑 src/main/java/com/fishpro/xmldom4j/util/SingleValueCalendarConverter.java)

/**
 * 日期轉換器
 * */
public class SingleValueCalendarConverter implements Converter {

    public void marshal(Object source, HierarchicalStreamWriter writer,
                        MarshallingContext context) {
        Calendar calendar = (Calendar) source;
        writer.setValue(String.valueOf(calendar.getTime().getTime()));
    }

    public Object unmarshal(HierarchicalStreamReader reader,
                            UnmarshallingContext context) {
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(new Date(Long.parseLong(reader.getValue())));
        return calendar;
    }

    public boolean canConvert(Class type) {
        return type.equals(GregorianCalendar.class);
    }
}

在運行 main 示例,如下 created 節點變化了

<user>
  <id>1</id>
  <username>fishpro</username>
  <address>
    <street>江蘇省南京市玄武大道1000號</street>
    <zipcode>201001</zipcode>
    <mobile>1801989098</mobile>
  </address>
  <address>
    <street>江蘇省南京市玄武大道1001號</street>
    <zipcode>201001</zipcode>
    <mobile>1811989098</mobile>
  </address>
  <created>1565691762404</created>
</user>

4.10 反序列化

 XStream xstream = new XStream();
        xstream.alias("person", Person.class);//設置節點person的名稱
        xstream.alias("phonenumber", PhoneNumber.class);
        Person joe = new Person("Joe", "Walnes");
        joe.setPhone(new PhoneNumber(123, "1234-456"));
        joe.setFax(new PhoneNumber(123, "9999-999"));
        String xml = xstream.toXML(joe);//對象轉 xml
        System.out.println(xml);
        /** 輸出簡單示例xml
         * <person>
         *   <firstname>Joe</firstname>
         *   <lastname>Walnes</lastname>
         *   <phone>
         *     <code>123</code>
         *     <number>1234-456</number>
         *   </phone>
         *   <fax>
         *     <code>123</code>
         *     <number>9999-999</number>
         *   </fax>
         * </person>
         * */
        Person newJoe = (Person)xstream.fromXML(xml);//xml 轉 對象

問題:

Exception in thread "main" java.lang.NoClassDefFoundError: org/jaxen/JaxenException

需要引入

<!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.1.6</version>
</dependency>

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM