1. 概述

在本教程中,我们将学习如何使用XStream库将 Java 对象序列化为 XML。

2. 特点

使用 XStream 序列化和反序列化 XML 有很多有趣的好处:

  • 配置正确,它会生成非常 干净的 XML
  • 定制 XML 输出提供了重要的机会
  • 支持 对象图 ,包括循环引用
  • 对于大多数用例, 一旦配置,XStream 实例就是线程安全的 (使用注释时有注意事项)
  • 异常处理 期间提供清晰的消息以帮助诊断问题
  • 从版本 1.4.7 开始,我们提供了 安全功能 来禁止某些类型的序列化

3. 项目设置

为了在我们的项目中使用 XStream,我们将添加以下 Maven 依赖项:

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.4.9</version>
</dependency>

4. 基本使用

XStream 类是 API 的外观。创建 XStream 实例时,我们还需要注意线程安全问题:

XStream xstream = new XStream();

创建并配置实例后,除非您启用注释处理,否则它可以在多个线程之间共享以进行编组/解组。

4.1.驱动

支持多种驱动程序,例如 DomDriverStaxDriverXppDriver 等。这些驱动程序具有不同的性能和资源使用特征。

默认使用XPP3驱动程序,但是我们当然可以轻松更改驱动程序:

XStream xstream = new XStream(new StaxDriver());

4.2.生成 XML

让我们首先为 – Customer 定义一个简单的 POJO:

public class Customer {

    private String firstName;
    private String lastName;
    private Date dob;

    // standard constructor, setters, and getters
}

现在让我们生成该对象的 XML 表示形式:

Customer customer = new Customer("John", "Doe", new Date());
String dataXml = xstream.toXML(customer);

使用默认设置,将产生以下输出:

<com.baeldung.pojo.Customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 03:46:16.381 UTC</dob>
</com.baeldung.pojo.Customer>

从这个输出中,我们可以清楚地看到包含标签默认使用 Customer 的完全限定类名*.*

我们可能会出于多种原因认为默认行为不适合我们的需求。例如,我们可能不愿意公开应用程序的包结构。此外,生成的 XML 明显更长。

5. 别名

别名 是我们希望用于元素的名称,而不是使用默认名称。

例如,我们可以通过注册 Customer 类的别名来将 com.baeldung.pojo.Customer 替换为 customer 。我们还可以为类的属性添加别名。通过使用别名,我们可以使 XML 输出更具可读性并且减少 Java 特定性。

5.1.类别名

别名可以通过编程方式或使用注释来注册。

现在让我们用 @XStreamAlias 注释我们的 Customer 类:

@XStreamAlias("customer")

现在我们需要配置我们的实例以使用此注释:

xstream.processAnnotations(Customer.class);

或者,如果我们希望以编程方式配置别名,我们可以使用下面的代码:

xstream.alias("customer", Customer.class);

无论使用别名还是编程配置, Customer 对象的输出都会更加清晰:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 03:46:16.381 UTC</dob>
</customer>

5.2.字段别名

我们还可以使用用于别名类的相同注释为字段添加别名。例如,如果我们希望在 XML 表示中将字段 firstName 替换为 fn ,我们可以使用以下注释:

@XStreamAlias("fn")
private String firstName;

或者,我们可以通过编程实现相同的目标:

xstream.aliasField("fn", Customer.class, "firstName");

aliasField 方法接受三个参数:我们希望使用的别名、定义属性的类以及我们希望别名的属性名称。

无论使用哪种方法,输出都是相同的:

<customer>
    <fn>John</fn>
    <lastName>Doe</lastName>
    <dob>1986-02-14 03:46:16.381 UTC</dob>
</customer>

5.3.默认别名

有几个为类预先注册的别名 - 以下是其中的一些:

alias("float", Float.class);
alias("date", Date.class);
alias("gregorian-calendar", Calendar.class);
alias("url", URL.class);
alias("list", List.class);
alias("locale", Locale.class);
alias("currency", Currency.class);

6. 集合

现在我们将在 Customer 类中添加 ContactDetails 列表。

private List<ContactDetails> contactDetailsList;

使用集合处理的默认设置,输出如下:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 04:14:05.874 UTC</dob>
    <contactDetailsList>
        <ContactDetails>
            <mobile>6673543265</mobile>
            <landline>0124-2460311</landline>
        </ContactDetails>
        <ContactDetails>
            <mobile>4676543565</mobile>
            <landline>0120-223312</landline>
        </ContactDetails>
    </contactDetailsList>
</customer>

假设我们需要省略 contactDetailsList 父标签*,*,并且我们只希望每个 ContactDetails 元素成为 customer 元素的子元素。让我们再次修改我们的示例:

xstream.addImplicitCollection(Customer.class, "contactDetailsList");

现在,生成 XML 时,会省略根标记,从而生成以下 XML:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>1986-02-14 04:14:20.541 UTC</dob>
    <ContactDetails>
        <mobile>6673543265</mobile>
        <landline>0124-2460311</landline>
    </ContactDetails>
    <ContactDetails>
        <mobile>4676543565</mobile>
        <landline>0120-223312</landline>
    </ContactDetails>
</customer>

使用注释也可以实现同样的效果:

@XStreamImplicit
private List<ContactDetails> contactDetailsList;

7.转换器

XStream 使用 Converter 实例的映射,每个实例都有自己的转换策略。它们将提供的数据转换为 XML 中的特定格式,然后再转换回来。

除了使用默认转换器之外,我们还可以修改默认值或注册自定义转换器。

7.1.修改现有转换器

假设我们对使用默认设置生成 dob 标签的方式不满意。我们可以修改 XStream 提供的自定义 Date 转换器( DateConverter ):

xstream.registerConverter(new DateConverter("dd-MM-yyyy", null));

上面将产生“ dd-MM-yyyy ”格式的输出:

<customer>
    <firstName>John</firstName>
    <lastName>Doe</lastName>
    <dob>14-02-1986</dob>
</customer>

7.2.定制转换器

我们还可以创建一个自定义转换器来完成与上一节相同的输出:

public class MyDateConverter implements Converter {

    private SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");

    @Override
    public boolean canConvert(Class clazz) {
        return Date.class.isAssignableFrom(clazz);
    }

    @Override
    public void marshal(
      Object value, HierarchicalStreamWriter writer, MarshallingContext arg2) {
        Date date = (Date)value;
        writer.setValue(formatter.format(date));
    }

    // other methods
}

最后,我们注册 MyDateConverter 类,如下所示:

xstream.registerConverter(new MyDateConverter());

我们还可以创建实现 SingleValueConverter 接口的转换器,该接口旨在将对象转换为字符串。

public class MySingleValueConverter implements SingleValueConverter {

    @Override
    public boolean canConvert(Class clazz) {
        return Customer.class.isAssignableFrom(clazz);
    }

    @Override
    public String toString(Object obj) {
        SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy");
        Date date = ((Customer) obj).getDob();
        return ((Customer) obj).getFirstName() + "," 
          + ((Customer) obj).getLastName() + ","
          + formatter.format(date);
    }

    // other methods
}

最后,我们注册 MySingleValueConverter

xstream.registerConverter(new MySingleValueConverter());

使用 MySingleValueConverter客户 的 XML 输出如下:

<customer>John,Doe,14-02-1986</customer>

7.3.转换器优先级

注册 Converter 对象时,也可以设置它们的优先级。

来自XStream javadocs

可以使用显式优先级来注册转换器。默认情况下,它们注册到 XStream.PRIORITY_NORMAL。相同优先级的转换器将按其注册的相反顺序使用。默认转换器,即如果没有其他已注册的转换器合适时将使用的转换器,可以使用优先级 XStream.PRIORITY_VERY_LOW 进行注册。 XStream 默认使用 ReflectionConverter 作为后备转换器。

API 提供了几个指定的优先级值:

private static final int PRIORITY_NORMAL = 0;
private static final int PRIORITY_LOW = -10;
private static final int PRIORITY_VERY_LOW = -20;

8. 省略字段

我们可以使用注释或编程配置从生成的 XML 中省略字段。为了使用注释省略字段,我们只需将 @XStreamOmitField 注释应用于相关字段:

@XStreamOmitField 
private String firstName;

为了以编程方式省略该字段,我们使用以下方法:

xstream.omitField(Customer.class, "firstName");

无论我们选择哪种方法,输出都是相同的:

<customer> 
    <lastName>Doe</lastName> 
    <dob>14-02-1986</dob> 
</customer>

9. 属性字段

有时我们可能希望将字段序列化为元素的属性而不是元素本身。假设我们添加一个 contactType 字段:

private String contactType;

如果我们想将 contactType 设置为 XML 属性,我们可以使用 @XStreamAsAttribute 注释:

@XStreamAsAttribute
private String contactType;

或者,我们可以通过编程实现相同的目标:

xstream.useAttributeFor(ContactDetails.class, "contactType");

上述任一方法的输出都是相同的:

<ContactDetails contactType="Office">
    <mobile>6673543265</mobile>
    <landline>0124-2460311</landline>
</ContactDetails>

10. 并发

XStream 的处理模型提出了一些挑战。一旦实例被配置,它就是线程安全的。

值得注意的是,注释的处理会在编组/解组之前修改配置。因此,如果我们需要使用注释动态配置实例,那么为每个线程使用单独的 XStream 实例通常是一个好主意。

11. 结论

在本文中,我们介绍了使用 XStream 将对象转换为 XML 的基础知识。我们还了解了可用于确保 XML 输出满足我们的需求的自定义。最后,我们研究了注释的线程安全问题。

在本系列的下一篇文章中,我们将了解如何将 XML 转换回 Java 对象。

可以从链接的GitHub 存储库下载本文的完整源代码。