跳至主要內容

使用 XML 工具: xstream

Steven大约 1 分钟xmljavajaxb

Xstream 是一种 OXMapping 技术,是用来处理 XML 文件序列化的框架。

JDK jaxb 的封装。

XStream User Guide: Converting Objects to XML
XStream 教程

官网: https://x-stream.github.io/tutorial.htmlopen in new window

https://github.com/LawssssCat/blog/tree/master/src/zh/dev-java-xml/code/demo-java-xml/n13-springoxm-usage/test/java/org/example/

编程式

测试类
package example;

import com.thoughtworks.xstream.XStream;
import lombok.extern.slf4j.Slf4j;
import org.example.entity.raw.Person;
import org.example.entity.raw.Site;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;

@Slf4j
public class XmlXStreamTest extends AbstractXmlXStreamCommonTest {
    static XStream xStream; // 线程安全
    @BeforeAll
    static void beforeAll() {
        xStream = new XStream();
        // 自定义标签名 —— "类/属性混叠"
        xStream.alias("person", Person.class); // 否则 <org.example.Person> 而不是 <person>
        xStream.aliasField("姓名", Person.class, "name"); // <name> to <姓名> in Person.class
        // 自定义包前缀 ——+ "包混叠"
        xStream.aliasPackage("my", "org.example.entity.raw"); // org.example.entity.raw.Site -> my.Site
        // 生成属性,而非子标签
        xStream.useAttributeFor(Person.class, "age"); // 属性 <person age="18">
        // 去掉集合标签 —— "隐式集合混叠"
        xStream.addImplicitCollection(Person.class, "sites"); // 去掉 <sites>
        // 忽略字段
        xStream.omitField(Site.class, "description");
    }

    public static Person newPerson() {
        Person person = new Person();
        person.setName("steven");
        person.setAge(18);
        person.setSites(new ArrayList<Site>());
        {
            Site site = new Site();
            site.setId("111");
            site.setUrl("http://n1.example.org");
            site.setDescription("description1");
            person.getSites().add(site);
        }
        {
            Site site = new Site();
            site.setId("222");
            site.setUrl("https://n2.example.org");
            site.setDescription("description2");
            person.getSites().add(site);
        }
        return person;
    }

    /**
     * 实体类 to xml
     */
    @Test
    @Order(1)
    void testObj2Xml() {
        Person person = newPerson();
        super.testObj2Xml(xStream, person);
    }

    /**
     * xml to 实体类
     */
    @Test
    @Order(2)
    void testXml2Obj() {
        Person person = newPerson();
        super.testXml2Obj(xStream, Person.class, new Class[] {Person.class, Site.class}, person);
    }
}

注解式

测试类
package example;

import com.thoughtworks.xstream.XStream;
import lombok.extern.slf4j.Slf4j;
import org.example.entity.anno.Person;
import org.example.entity.anno.Site;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;

@Slf4j
public class XmlXStreamAnnotationTest extends AbstractXmlXStreamCommonTest {
    static XStream xStream;

    @BeforeAll
    static void beforeAll() {
        xStream = new XStream(); // 线程安全
        xStream.autodetectAnnotations(true);
    }

    public static Person newPerson() {
        Person person = new Person();
        person.setName("steven");
        person.setAge(18);
        person.setSites(new ArrayList<Site>());
        {
            Site site = new Site();
            site.setId("111");
            site.setUrl("http://n1.example.org");
            person.getSites().add(site);
        }
        {
            Site site = new Site();
            site.setId("222");
            site.setUrl("https://n2.example.org");
            person.getSites().add(site);
        }
        return person;
    }

    /**
     * 实体类 to xml
     */
    @Test
    @Order(1)
    void testObj2Xml() {
        Person person = newPerson();
        super.testObj2Xml(xStream, person);
    }

    /**
     * xml to 实体类
     */
    @Test
    @Order(2)
    void testXml2Obj() {
        Person person = newPerson();
        super.testXml2Obj(xStream, Person.class, new Class[] {Person.class, Site.class},  person);
    }
}

自定义转换器

转换器
package org.example.converter;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import lombok.extern.slf4j.Slf4j;
import org.example.entity.anno.Site;

@Slf4j
public class SiteConverter implements Converter {
    @Override
    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
        if (source instanceof Site) {
            Site site = (Site) source;
            writer.startNode("my-url");
            writer.setValue(site.getUrl());
            writer.endNode();
            writer.startNode("my-id");
            writer.setValue(site.getId());
            writer.endNode();
        }
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
        Site site = new Site();
        while(reader.hasMoreChildren()) {
            reader.moveDown();
            switch (reader.getNodeName()) {
                case "my-url":
                    site.setUrl(reader.getValue());
                    break;
                case "my-id":
                    site.setId(reader.getValue());
            }
            reader.moveUp();
        }
        return site;
    }

    @Override
    public boolean canConvert(Class type) {
        return type.isAssignableFrom(Site.class);
    }
}

转换 JSON

添加依赖

<dependency>
    <groupId>org.codehaus.jettison</groupId>
    <artifactId>jettison</artifactId>
    <version>1.5.4</version>
</dependency>
测试类
package example;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;
import org.example.entity.anno.Person;
import org.example.entity.anno.Site;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Objects;

public class XmlXStreamJsonTest extends AbstractXmlXStreamCommonTest{
    {
        file = new File(Objects.requireNonNull(getClass().getResource("/")).getFile(), getClass().getName() + ".json");
    }

    static XStream xStream;

    @BeforeAll
    static void beforeAll() {
        // 可以选择不同的数据转换器,如 DomDriver、StaxDriver、JDomDriver 等,用于控制 XML 数据的读写方式。
        HierarchicalStreamDriver driver = new JettisonMappedXmlDriver(); // 引入 JSON 转换驱动
        xStream = new XStream(driver);
        // 可以通过 setMode() 方法设置不同的模式,如 NO_REFERENCES、ID_REFERENCES、HIERARCHICAL、SINGLE_NODE 等,用于处理对象之间的引用关系。
        // xStream.setMode(XStream.NO_REFERENCES);
        xStream.autodetectAnnotations(true);
    }

    /**
     * 实体类 to xml
     */
    @Test
    @Order(1)
    void testObj2Xml() throws IOException {
        Person person = XmlXStreamAnnotationTest.newPerson();
        try (OutputStreamWriter fileOutputStream = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8)) {
            testObj2Xml(xStream, person, fileOutputStream);
        }
    }

    /**
     * xml to 实体类
     */
    @Test
    @Order(2)
    void testXml2Obj() {
        Person person = XmlXStreamAnnotationTest.newPerson();
        super.testXml2Obj(xStream, Person.class, new Class[] {Person.class, Site.class},  person);
    }
}

对象流

测试类
package example;

import com.thoughtworks.xstream.XStream;
import lombok.extern.slf4j.Slf4j;
import org.example.entity.anno.Person;
import org.example.entity.anno.Site;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

/**
 * XStream 流式输出
 */
@Slf4j
public class XmlXStreamObjectIOTest extends AbstractXmlXStreamCommonTest {
    static XStream xStream;

    @BeforeAll
    static void beforeAll() {
        xStream = new XStream(); // 线程安全
        xStream.autodetectAnnotations(true);
        xStream.allowTypes(new Class[] {Person.class, Site.class});
    }

    public static List<Object> newListObject() {
        List<Object> expects = new ArrayList<>();
        expects.add(XmlXStreamAnnotationTest.newPerson());
        expects.add(XmlXStreamAnnotationTest.newPerson());
        expects.add("hello world!");
        expects.add(1024);
        return expects;
    }

    /**
     * 持续写出对象
     */
    @Test
    @Order(1)
    void testObjectOutputStream() throws IOException {
        try (FileOutputStream fileInputStream = new FileOutputStream(file)) {
            ObjectOutputStream objectOutputStream = xStream.createObjectOutputStream(fileInputStream);
            for (Object e : newListObject()) {
                if (e instanceof Integer) {
                    objectOutputStream.writeInt((Integer) e);
                } else if (e instanceof String) {
                    objectOutputStream.writeUTF((String) e);
                } else {
                    objectOutputStream.writeObject(e);
                }
            }
            objectOutputStream.close();
        }
        Assertions.assertTrue(file.exists());
        Files.readAllLines(file.toPath()).forEach(log::info);
    }

    /**
     * 持续读入对象
     */
    @Test
    @Order(2)
    void testObjectInputStream() throws IOException, ClassNotFoundException {
        List<Object> real = new ArrayList<>();
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            ObjectInputStream objectInputStream = xStream.createObjectInputStream(fileInputStream);
            for (Object e : newListObject()) {
                Object r = null;
                if (e instanceof Integer) {
                    r = objectInputStream.readInt();
                } else if (e instanceof String) {
                    r = objectInputStream.readUTF();
                } else {
                    r = objectInputStream.readObject();
                }
                Assertions.assertEquals(e, r);
                real.add(r);
            }
            objectInputStream.close();
        }
        Assertions.assertEquals(newListObject(), real);
    }
}

对象持久化

持久化后,会生成 int@0.xml、int@1.xml、int@2.xml 这样的文件。这些文件内容就是数组里的内容。

测试类
package example;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.persistence.FilePersistenceStrategy;
import com.thoughtworks.xstream.persistence.XmlArrayList;
import org.example.entity.anno.Person;
import org.example.entity.anno.Site;
import org.junit.jupiter.api.*;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * 持久化对象内容
 */
public class XmlXStreamObjectPersistentTest extends AbstractXmlXStreamCommonTest {
    {
        // 需要为一个目录
        file = new File(Objects.requireNonNull(getClass().getResource("/")).getFile(), getClass().getName());
        file.mkdirs();
        Assertions.assertTrue(file.exists());
        Assertions.assertTrue(file.isDirectory());
    }

    static XStream xStream;

    static List<Object> temp = new ArrayList<>();

    @BeforeAll
    static void beforeAll() {
        xStream = new XStream(); // 线程安全
        xStream.autodetectAnnotations(true);
        xStream.allowTypes(new Class[] {Person.class, Site.class});
    }

    static FilePersistenceStrategy filePersistenceStrategy;


    @BeforeEach
    void beforeEach() {
        filePersistenceStrategy = new FilePersistenceStrategy(file, xStream);
    }

    /**
     * 持久化1
     */
    @Test
    @Order(1)
    void testFilePersistenceStrategySave1() {
        List xmlArrayList = new XmlArrayList(filePersistenceStrategy);
        List<Object> objects = XmlXStreamObjectIOTest.newListObject();
        xmlArrayList.addAll(objects);
        temp.addAll(objects);
    }

    /**
     * 持久化1
     */
    @Test
    @Order(2)
    void testFilePersistenceStrategySave2() {
        List xmlArrayList = new XmlArrayList(filePersistenceStrategy);
        Person person = XmlXStreamAnnotationTest.newPerson();
        xmlArrayList.add(person);
        temp.add(person);
    }

    @Test
    @Order(11)
    void testFilePersistenceStrategyLoad1() {
        List xmlArrayList = new XmlArrayList(filePersistenceStrategy);
        int count = xmlArrayList.size();
        Iterator iterator = xmlArrayList.iterator();
        while (iterator.hasNext()) {
            iterator.next();
        }
        Assertions.assertEquals(temp.size(), count);
    }

    @AfterAll
    static void afterAll() {
        // 清空,初始化环境
        if (temp.isEmpty()) {
            List xmlArrayList = new XmlArrayList(filePersistenceStrategy);
            Iterator iterator = xmlArrayList.iterator();
            while (iterator.hasNext()) {
                iterator.next();
                iterator.remove();
            }
        }
    }
}