XStream – XStreamely使用Java中的XML数据的简便方法

有时候,我们不得不处理XML数据。 而且大多数时候,这不是我们一生中最快乐的一天。 甚至有一个术语“ XML地狱”描述了程序员必须处理许多难以理解的XML配置文件时的情况。 但是,不管喜欢与否,有时我们别无选择,这主要是因为客户端的说明中提到了诸如“使用以XML文件编写的配置”之类的内容。 在这种情况下, XStream带有非常酷的功能,这些功能使处理XML的痛苦减轻了。

总览

XStream是一个小型库,用于在Java对象和XML之间序列化数据。 它重量轻,体积小,具有漂亮的API,最重要的是,它可以与自定义注释一起使用,也可以不包含不属于Java类所有者的自定义注释。

第一个例子

假设我们需要从xml文件加载配置:

<config>
    <inputFile>/Users/tomek/work/mystuff/input.csv</inputFile>
    <truststoreFile>/Users/tomek/work/mystuff/truststore.ts</truststoreFile>
    <keystoreFile>/Users/tomek/work/mystuff/CN-user.jks</keystoreFile>

    <!-- ssl stores passwords-->
    <truststorePassword>password</truststorePassword>
    <keystorePassword>password</keystorePassword>

    <!-- user credentials -->
    <user>user</user>
    <password>secret</password>
</config>

我们想将其加载到Configuration对象中:

public class Configuration {

    private String inputFile;
    private String user;
    private String password;

    private String truststoreFile;
    private String keystoreFile;
    private String keystorePassword;
    private String truststorePassword;

    // getters, setters, etc.
}

所以基本上我们要做的是:

FileReader fileReader = new FileReader("config.xml");  // load our xml file  
    XStream xstream = new XStream();     // init XStream
    // define root alias so XStream knows which element and which class are equivalent
    xstream.alias("config", Configuration.class);    
    Configuration loadedConfig = (Configuration) xstream.fromXML(fileReader);

仅此而已

更严重的事情

好的,但是前面的示例非常基础,因此现在让我们做一些更复杂的事情:由真实WebService返回的真实XML。

<DATA xmlns="">
    <BAN>
        <UPDATED_AT>2013-03-09</UPDATED_AT>
        <TROUBLEMAKER>
            <NAME1>JOHN</NAME1>
            <NAME2>EXAMPLE</NAME2>
            <AGE>24</AGE>
            <NUMBER>ASD123123</NUMBER>
        </TROUBLEMAKER>
    </BAN>
    <BAN>
        <UPDATED_AT>2012-03-10</UPDATED_AT>
        <TROUBLEMAKER>
            <NAME1>ANNA</NAME1>
            <NAME2>BAKER</NAME2>
            <AGE>26</AGE>
            <NUMBER>AXN567890</NUMBER>
        </TROUBLEMAKER>
    </BAN>
    <BAN>
        <UPDATED_AT>2010-12-05</UPDATED_AT>
        <TROUBLEMAKER>
            <NAME1>TOM</NAME1>
            <NAME2>MEADOW</NAME2>
            <NUMBER>SGH08945</NUMBER>
            <AGE>48</AGE>
        </TROUBLEMAKER>
    </BAN>
</DATA>

我们这里提供的是用XML编写的简单禁令清单。 我们希望将其加载到Ban对象的集合中。 因此,让我们准备一些类(省略getters / setters / toString):

public class Data {
    private List bans = new ArrayList(); 
}

public class Ban {
    private String dateOfUpdate;
    private Person person;
}

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private String documentNumber;
}

如您所见,XML和Java类之间存在一些命名和类型不匹配的问题(例如,字段名1-> firstName,dateOfUpdate是String而不是Date),但这是出于某些示例目的。 因此,这里的目标是解析XML并使用包含正确数据的Ban实例的填充集合来获取Data对象。 让我们看看如何实现它。

解析注释

首先,更简单的方法是使用注释。 这是在我们可以修改将XML映射到的Java类的情况下的建议方法。
因此,我们有:

@XStreamAlias("DATA") // maps DATA element in XML to this class
public class Data {

    // Here is something more complicated. If we have list of elements that are 
    // not wrapped in a element representing a list (like we have in our XML: 
    // multiple <ban> elements not wrapped inside <bans> collection, 
    // we have to declare that we want to treat these elements as an implicit list 
    // so they can be converted to List of objects. 
    @XStreamImplicit(itemFieldName = "ban") 
    private List bans = new ArrayList();
}

@XStreamAlias("BAN") // another mapping
public class Ban {

    /*
     We want to have different field names in Java classes so
     we define what element should be mapped to each field
    */
    @XStreamAlias("UPDATED_AT") // 
    private String dateOfUpdate;

    @XStreamAlias("TROUBLEMAKER")
    private Person person;
}

@XStreamAlias("TROUBLEMAKER")
public class Person {

    @XStreamAlias("NAME1")
    private String firstName;

    @XStreamAlias("NAME2")
    private String lastName;

    @XStreamAlias("AGE") // String will be auto converted to int value
    private int age;

    @XStreamAlias("NUMBER")
    private String documentNumber;

实际的解析逻辑非常简短:

FileReader reader = new FileReader("file.xml");  // load file

    XStream xstream = new XStream();
    xstream.processAnnotations(Data.class);     // inform XStream to parse annotations in Data class
    xstream.processAnnotations(Ban.class);      // and in two other classes... 
    xstream.processAnnotations(Person.class);   // we use for mappings
    Data data = (Data) xstream.fromXML(reader); // parse

    // Print some data to console to see if results are correct
    System.out.println("Number of bans = " + data.getBans().size());
    Ban firstBan = data.getBans().get(0);
    System.out.println("First ban = " + firstBan.toString());

如您所见,注释非常易于使用,因此最终代码非常简洁。 但是在无法修改映射类的情况下该怎么办? 我们可以使用不需要对表示XML数据的Java类进行任何修改的其他方法。

解析无注释

当我们无法用注解充实我们的模型类时,还有另一种解决方案。 我们可以使用XStream对象的方法定义所有映射详细信息:

FileReader reader = new FileReader("file.xml");  // three first lines are easy, 
    XStream xstream = new XStream();                 // same initialisation as in the 
    xstream.alias("DATA", Data.class);               // basic example above

    xstream.alias("BAN", Ban.class);             // two more aliases to map... 
    xstream.alias("TROUBLEMAKER", Person.class); // between node names and classes

    // We want to have different field names in Java classes so
    // we have to use aliasField(<fieldInXml>, <mappedJavaClass>, <mappedFieldInJavaClass>)
    xstream.aliasField("UPDATED_AT", Ban.class, "dateOfUpdate"); 
    xstream.aliasField("TROUBLEMAKER", Ban.class, "person");

    xstream.aliasField("NAME1", Person.class, "firstName");
    xstream.aliasField("NAME2", Person.class, "lastName");
    xstream.aliasField("AGE", Person.class, "age");  // notice here that XML will be auto-converted to int "age"
    xstream.aliasField("NUMBER", Person.class, "documentNumber");

    /*
     Another way to define implicit collection
    */ 
    xstream.addImplicitCollection(Bans.class, "bans");

    Data data = (Data) xstream.fromXML(reader);  // do the actual parsing

    // let's print results to check if data was parsed
    System.out.println("Number of bans = " + data.getBans().size());
    Ban firstBan = data.getBans().get(0);
    System.out.println("First ban = " + firstBan.toString());

如您所见,XStream可以轻松地将更复杂的XML结构转换为Java对象,如果XML不能满足我们的需求,它还可以通过使用不同的名称来调整结果。 但是有一件事情应该引起您的注意:我们正在将表示Date的XML转换为原始String,这与我们想要的结果不完全相同。 这就是为什么我们将添加转换器来为我们做一些工作的原因。

使用现有的自定义类型转换器

XStream库附带了一组针对大多数常见用例的内置转换器。 我们将使用DateConverter。 所以现在我们班的班级看起来像这样:

public class Ban {

    private Date dateOfUpdate;
    private Person person;
}

要使用DateConverter,我们只需将其注册为我们期望出现在XML数据中的日期格式即可:

xstream.registerConverter(new DateConverter("yyyy-MM-dd", new String[] {}));

就是这样。 现在,我们的对象不是String,而是使用Date实例填充的。 酷又容易! 但是,现有转换器未涵盖的类和情况又如何呢? 我们可以自己写。

从头开始编写自定义转换器

假设我们想知道几天前完成更新,而不是dateOfUpdate:

public class Ban {
    private int daysAgo;
    private Person person;
}

当然,我们可以为每个Ban对象手动计算它,但是使用可以为我们完成这项工作的转换器看起来会更加有趣。 我们的DaysAgoConverter必须实现Converter接口,因此我们必须实现三种带有签名的方法,这些方法看起来有些吓人:

public class DaysAgoConverter implements Converter {
    @Override
    public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
    }

    @Override
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    }

    @Override
    public boolean canConvert(Class type) {
        return false;
    }
}

最后一个很容易,因为我们将只转换Integer类。 但是,这些HierarchicalStreamWriter仍然剩下两种方法,MarshallingContext,HierarchicalStreamReader和UnmarshallingContext参数。 幸运的是,我们可以通过使用AbstractSingleValueConverter来避免与它们打交道,它使我们免受如此低级的机制的影响。 现在我们的班级看起来好多了:

public class DaysAgoConverter extends AbstractSingleValueConverter {

    @Override
    public boolean canConvert(Class type) {
        return type.equals(Integer.class);
    }

    @Override
    public Object fromString(String str) {
        return null;
    }

    public String toString(Object obj) {
        return null;
    }
}

另外,我们必须重写AbstractSingleValueConverter中定义的toString(Object obj)方法,因为我们要将Date存储在从Integer计算的XML中,而不是简单的Object.toString值,该值将从抽象父级中定义的默认toString返回。

实作

下面的代码非常简单,但是大多数有趣的行都被注释了。 我跳过了所有验证内容,以简化此示例。

public class DaysAgoConverter extends AbstractSingleValueConverter {

    private final static String FORMAT = "yyyy-MM-dd"; // default Date format that will be used in conversion
    private final DateTime now = DateTime.now().toDateMidnight().toDateTime(); // current day at midnight

    public boolean canConvert(Class type) {
        return type.equals(Integer.class);     // Converter works only with Integers
    }

    @Override
    public Object fromString(String str) {
        SimpleDateFormat format = new SimpleDateFormat(FORMAT);
        try {
            Date date = format.parse(str);
            return Days.daysBetween(new DateTime(date), now).getDays();  // we simply calculate days between using JodaTime
        } catch (ParseException e) {
            throw new RuntimeException("Invalid date format in " + str);
        }
    }

    public String toString(Object obj) {
        if (obj == null) {
            return null;
        }

        Integer daysAgo = ((Integer) obj);
        return now.minusDays(daysAgo).toString(FORMAT); // here we subtract days from now and return formatted date string
    }
}
用法

要将自定义转换器用于特定字段,我们必须使用registerLocalConverter通知它XStream对象:

xstream.registerLocalConverter(Ban.class, "daysAgo", new DaysAgoConverter());

我们使用“本地”方法将此转换仅应用于特定字段,而不应用于XML文件中的每个Integer字段。 然后,我们将使用天数而不是日期填充Ban对象。

摘要

这就是我想在这篇文章中向您展示的全部内容。 现在,您已掌握有关XStream的功能以及如何将其轻松地将XML数据映射到Java对象的基本知识。如果您需要更高级的内容,请查看项目官方页面,因为它包含非常好的文档和示例。

参考: XStream –来自Code Hard Go Pro博客的JCG合作伙伴 Tomasz Dziurko的XStreamely 简单方法,可使用Java处理XML数据

翻译自: https://www.javacodegeeks.com/2013/04/xstream-xstreamely-easy-way-to-work-with-xml-data-in-java.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值