真正简化从 Java 对象转换到 XML 的过程
Java™ 开发人员在序列化和反序列化可扩展标记语言(Extensible Markup Language,XML)对象时有多种不同的选择。Simple 就是这样的一个例子,并且它有很多超越其他竞争方案的优势。本文概要介绍如何在 XML 通信系统中使用 Simple。
Simple 是什么?
Simple 是一个 Java 框架,用于简化序列化和反序列化 XML 的过程。使用 Simple,开发人员可以简化(名字由此而来)将简单老 Java 对象(POJO)转换成 XML 文档的过程 — 即所谓的序列化(serialization)过程。Simple 也可促进相反的过程:开发人员可以将 XML 文档转换成 POJO — 即所谓的反序列化(deserialization)过程。
Simple 名副其实,它使用注解来支持序列化和反序列化过程。根据相应的 XML 文档应该如何出现,对 POJO 进行注解。一些字段被注解为属性,另一些字段被注解为元素。类通常被注解为根元素。在序列化过程中,该框架处理冗长的解释注解和产生相应 XML 文档的过程。无足为奇,在 XML 文档被转换成 POJO 的反序列化过程中,注解也会被解释。
使用 Simple 有几个优势。首先,它促进快速应用程序开发。Simple 是如此简单,它支持开发人员快速实现健壮的、使用 XML 序列化和反序列化的应用程序,无需完成长长的学习曲线以及付出繁重的开发劳动。
其次,Simple 不需要配置。前面已经提到,Simple 使用注解。这些注解取代了基于 XML 的配置文件(其他框架一般都有这样的配置文件)。
最后,Simple 只让使用它的应用程序增加少量的内存占用。Java 归档(Java Archive,JAR)文件只有 239 KB。Simple 也不依赖于一系列其他 JAR 文件,而其他框架则通常不是这样的。
获得 Simple
必须首先访问 参考资源 中列出的 Simple 网站下载归档文件,获得 Simple,然后才能使用它。但是关于这个过程,既有好消息,也有坏消息。好消息是,归档文件是免费的。坏消息是,归档文件是 .tar.gz 格式的。所以,您若是想用 Microsoft® Windows® 本机归档文件解压程序来打开这个归档文件的话,是会失望的。需要使用 WinZip 或其他类似的归档工具。
解压文件之后,注意在 jar 目录中有一个 JAR 文件(simple-xml-2.1.4.jar)。在编译时和运行时,类路径中需要有这个 JAR 文件。
序列化
将一个对象序列化成一个 XML 文档是一个相当简单的过程。涉及到两步:
- 创建具有适当注解的 POJO。
- 编写不多的几行代码,用于真正执行序列化过程。
为了本文目的,我们来回顾一下熟悉的鱼饵主题(当然只有读过我的文章的读者熟悉)。所以,清单 1 是一个表示鱼饵信息的 POJO。
清单 1. Lure 类
@Root public class Lure { @Attribute private String type; @Element private String company; @Element private int quantityInStock; @Element private String model; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getCompany() { return company; } public void setCompany(String company) { this.company = company; } public int getQuantityInStock() { return quantityInStock; } public void setQuantityInStock(int quantityInStock) { this.quantityInStock = quantityInStock; } public String getModel() { return model; } public void setModel(String model) { this.model = model; } }
这个 POJO 真的没有什么复杂的东西。其唯一可能初看起来不熟悉的部分是注解。同样,这是故意的。回想一下,Simple 框架的目的就是实现它名字的寓意。
@Root
注解描述 XML 文档的根元素。因为每个 XML 文档都需要一个根元素,所以一定要记得包含这个元素。
type
字段上面的 @Attribute
注解将该字段识别为属性。该属性被作为一个属性添加到根元素。
其余注解是 @Element
注解。这些注解位于以下 3 个字段的正上方:company
、quantityInStock
和 model
。这些字段代表 XML 文档中的元素。
根据 JavaBean 标准,该 POJO 的其余部分由访问器(accessor)方法和 修改器(mutator)方法组成。
既然 POJO 已经创建完成了,下面就该编写序列化代码了。参见 清单 2 中的该代码。
清单 2. LureExample 类
public static void main(String[] args) { try { Serializer serializer = new Persister(); Lure lure = new Lure(); lure.setCompany("Donzai"); lure.setModel("Marlin Buster"); lure.setQuantityInStock(23); lure.setType("Trolling"); File result = new File("lure.xml"); serializer.write(lure,result); } catch (Exception e) { e.printStackTrace(); } }
第一件事就是实例化一个 Persister
对象。注意,该类是 Simple 框架的一部分,它实现 Serializer
接口。
接下来,实例化 Lure
对象并设置适当的字段。在本例中,制造鱼饵的公司名称是 Donzai
,模型的名字是 Marlin Buster
,库存数量是 23
。最后,鱼饵的类型是 Trolling
。
再接下来,实例化一个 File
对象,它的名称是将成为 XML 文档的那个文件的名称。在本例中,该名称是 lure.xml。
最后,调用 serializer 来编写文件。write()
方法中提供两个参数。第一个参数是 POJO,第二个参数是 File
对象。
现在可以执行该代码了。清单 2 是一个 Java 应用程序,所以您可以使用自己喜欢的集成开发环境 (IDE) 来运行它。确保 simple-xml-2.1.4.jar 在类路径中。如果使用的是 Eclipse,只需在文件上右击,选择 Run As,再从出现的菜单中选择 Java Application 即可。
如果一切正常(应该如此 — 这很简单,是不是?),那么产生的 XML 文档应该类似于 清单 3。
清单 3. LureExample 的输出
<lure type="Trolling"> <company>Donzai</company> <quantityInStock>23</quantityInStock> <model>Marlin Buster</model> </lure>
注意关于 清单 3 的几点。首先,注意 lure 类型是根元素的一个属性。这没有疑义,因为前面在 POJO 中是用 @Attribute
而不是 @Element
注解了 type
字段。
关于产生的 XML,另一个重点是元素名称遵循 JavaBean 标准。例如,根元素是 lure
,尽管类名称是 Lure
。3 个子元素名称完全匹配字段名称。同样,这也是有意为之,因为您不想要首字母大写的根元素,而子元素名称则遵循另外的模式。
反序列化
序列化一个对象是如此地容易,那么反序列化对象也很容易,对不对?对!
回想一下,反序列化是将 XML 文档转换成 POJO 的过程。很不错的是,可以使用刚才创建的 XML 文档来进行反序列化。
清单 4. LureExample2 类
public static void main(String[] args) { try { Serializer serializer = new Persister(); File source = new File("lure.xml"); Lure lure = serializer.read(Lure.class,source); System.out.println(lure.getCompany()); System.out.println(lure.getModel()); System.out.println(lure.getQuantityInStock()); System.out.println(lure.getType()); } catch (Exception e) { e.printStackTrace(); } }
同样,首先是实例化一个实现 Serializer
接口的对象(Persister
)。
另一个类似行是 File
对象的实例化。但是,这里有一个明显的不同。在 清单 3 中,是为一个不存在的文件实例化了 File
对象。此处假设(实际上必须这样)该文件已经存在。
接下来,通过从 Serializer
对象调用 read
方法,从而实例化 POJO。read
方法有两个参数:POJO 的类和 File
对象,后者代表包含数据的 XML 文件。
最后,从 POJO 输出所有的信息,确保所有内容都读取正确。
清单 5. LureExample2 的输出
Donzai Marlin Buster 23 Trolling
全树序列化
至此,您所序列化和反序列化的 XML 文档都相当简单。
大多数 XML 文档要复杂多了。它们通常具有数层嵌套的元素,有时候还有一到多个重复的元素,每个元素又有几个子元素。
幸运的是,Simple 即使对于更复杂的文档也不违反它的名字的寓意。就像在一个 POJO 中嵌套又一个 POJO 那么简单。我们来看一下 清单 6。
清单 6. AdvancedLure 类
@Attribute private String type; @Element private String company; @Element private int quantityInStock; @Element private String model; @Element private ConfigurationScheme configurationScheme;
清单 6 中的 AdvancedLure
类非常类似于 清单 1 中描述的 Lure
类。一个例外之处是包含了另一个字段:configurationScheme
。配置方案由另一个 POJO 表示,描述在 清单 7 中。
清单 7. ConfigurationScheme 类
@Root public class ConfigurationScheme { @Element private String color; @Element private int size;
为节省篇幅,我舍去了 accessor 方法和 mutator 方法。
清单 7 中的注解应该看起来很熟悉。它们与 清单 1 中使用的是相同的注解。
这又将我们引向 清单 8。
清单 8. LureExample3 类
public static void main(String[] args) { try { Serializer serializer = new Persister(); AdvancedLure lure = new AdvancedLure(); lure.setCompany("Donzai"); lure.setModel("Marlin Buster"); lure.setQuantityInStock(23); lure.setType("Trolling"); ConfigurationScheme configurationScheme = new ConfigurationScheme(); configurationScheme.setColor("Blue"); configurationScheme.setSize(3); lure.setConfigurationScheme(configurationScheme); File result = new File("advancedlure.xml"); serializer.write(lure,result); } catch (Exception e) { e.printStackTrace(); } }
清单 8 和 清单 2 之间最大的差别是,您在 AdvancedLure
对象中实例化了一个 ConfigurationScheme
对象并设置了适当的字段。
清单 9. LureExample3 的输出
<advancedLure type="Trolling"> <company>Donzai</company> <quantityInStock>23</quantityInStock> <model>Marlin Buster</model> <configurationScheme> <color>Blue</color> <size>3</size> </configurationScheme> </advancedLure>
可以看到,嵌套的元素与预期那样地被序列化。
枚举
XML 文档经常包含某些元素的枚举。也就是说,相同的元素名称可以重复,只不过带有不同的子元素或属性数据而已。幸运的是,Simple 也能够处理这种情况。
为了演示该特性,我们创建一个新的类,名叫 Inventory
,其中包含仓库(储存库存的地方)的名称和在该库存位置找到的鱼饵的列表,如 清单 10 中所示。
清单 10. Inventory 类
@Root public class Inventory { @ElementList private List<AdvancedLure> lures; @Attribute private String warehouse;
注意,使用了一个新注解:@ElementList
。该注解告诉 Simple 框架:相应的 List
对象代表 XML 元素的枚举。
用于演示该 POJO 的序列化的代码跟用于序列化更简单的 POJO(比如说 清单 2 中序列化的那个 POJO)的代码一样容易。唯一增加的复杂性来自于,为嵌套目的实例化了所有必需的对象。参见 清单 11。
清单 11. LureExample4 类
Serializer serializer = new Persister(); AdvancedLure lure = new AdvancedLure(); lure.setCompany("Donzai"); lure.setModel("Marlin Buster"); lure.setQuantityInStock(23); lure.setType("Trolling"); ConfigurationScheme configurationScheme = new ConfigurationScheme(); configurationScheme.setColor("Blue"); configurationScheme.setSize(3); lure.setConfigurationScheme(configurationScheme); AdvancedLure lure2 = new AdvancedLure(); lure2.setCompany("Ziggi"); lure2.setModel("Tuna Finder"); lure2.setQuantityInStock(44); lure2.setType("Trolling"); ConfigurationScheme configurationScheme2 = new ConfigurationScheme(); configurationScheme2.setColor("Red"); configurationScheme2.setSize(5); lure2.setConfigurationScheme(configurationScheme2); List<AdvancedLure> lures = new ArrayList<AdvancedLure>(); lures.add(lure); lures.add(lure2); Inventory inventory = new Inventory(); inventory.setLures(lures); inventory.setWarehouse("Midwest"); File result = new File("inventory.xml"); serializer.write(inventory,result);
执行该代码时,会创建一个叫做 inventory.xml 的 XML 文件。该文件的内容应该类似于 清单 12。
清单 12. LureExample4 的输出
<inventory warehouse="Midwest"> <lures> <advancedLure type="Trolling"> <company>Donzai</company> <quantityInStock>23</quantityInStock> <model>Marlin Buster</model> <configurationScheme> <color>Blue</color> <size>3</size> </configurationScheme> </advancedLure> <advancedLure type="Trolling"> <company>Ziggi</company> <quantityInStock>44</quantityInStock> <model>Tuna Finder</model> <configurationScheme> <color>Red</color> <size>5</size> </configurationScheme> </advancedLure> </lures> </inventory>
可以看到,输出完全模仿 清单 11 中实例化及创建的 POJO。其中有两个 advancedLure
元素,它们各自包含用来填充相应 POJO 的数据。注意,嵌套也是允许的。
构造器
您的代码有可能使用永久不变的 POJO。在这种情况下,您可能会缺少用于修改字段属性的 setter 方法,相反,您依靠构造器来设置这些值。Simple 也能够处理这种情况。
在这种情况下,注解指定在构造器参数中,而不是放在字段名称上面。在适当 accessor 方法上面也需要注解。参见 清单 13。
清单 13. Inventory2 类
public Inventory2(@ElementList(name="lures") List<AdvancedLure> lures,@Attribute(name="warehouse") String warehouse) { this.lures = lures; this.warehouse = warehouse; } @ElementList(name="lures") public List<AdvancedLure> getLures() { return lures; } @Attribute(name="warehouse") public String getWarehouse() { return warehouse; }
注意,在指定注解作为构造器参数的一部分时,也必须指定它对应的字段名称。对于第一个参数,字段名称是 lures
。对于第二个参数,字段名称是 warehouse
。
然后提供 accessor 方法上面所匹配的注解和字段名称。在 lures
的 getter 方法的正上方,可以看到适当的注解,完全匹配构造器中使用的注解。类似地,warehouse
的 getter 方法上面的注解也完全匹配该注解。
要执行该代码,只需对 清单 11 稍加修改,以包含 清单 14 中的代码。
清单 14. LureExample5 类
Inventory2 inventory = new Inventory2(lures,"MidWest");
使用 清单 14 中的代码,而不使用 清单 11 中使用的 setter 方法。其余都保持不变。
模板过滤器
Simple 的另一个伟大特性是,在反序列化 XML 文档时能够使用模板过滤器或默认值。
回到初始的 lure.xml,但是稍微修改一下,如 清单 15 所示。
清单 15. Lure2.xml 文档
<lure type="Trolling"> <company>${lure.company}</company> <quantityInStock>23</quantityInStock> <model>Marlin Buster</model> </lure>
注意 company 元素怎么不是包含实际的公司名称,而是包含一个类似于 Apache Ant 的变量声明?在本例中,变量的名称是 lure.company
。
模板过滤器允许您用在运行时建立的实际的值代替变量声明。做到这一点最容易的一种方式是,利用一个 Map
对象将变量名与它的值相关联。然后使用该 Map
对象从 Simple 框架构造一个 Filter
对象。然后再使用该 Filter
对象构造 Persister
对象。我们来看一下 清单 16。
清单 16. LureExample6 类
Map<String,String> map = new HashMap<String,String>(); map.put("lure.company","Monmouth"); Filter filter = new MapFilter(map); Serializer serializer = new Persister(filter); File source = new File("lure2.xml"); Lure lure = serializer.read(Lure.class,source); System.out.println(lure.getCompany()); System.out.println(lure.getModel()); System.out.println(lure.getQuantityInStock()); System.out.println(lure.getType());
清单 16 与 清单 2 并没有大的差别。主要差别是,实例化了一个 HashMap
,用于将一个 String
值与一个 String
键相关联。本例中的 String
键果然是 lure.company
,即 清单 15 中使用的变量名。这里给它分配的值是 Monmouth
,而不是前面 XML 文档中的 Donzai
。
清单 17. LureExample6 的输出
Monmouth Marlin Buster 23 Trolling
可以看到,模板过滤器用 Map
对象中与该键名关联的实际值取代了变量名 lure.company
。输出与预期完全一致。
结束语
如您所见,Simple 是一个健壮的 XML 序列化和反序列化框架。易于使用,无需配置,并且简化了开发工作。
Simple 有很多强大的特性,但是由于篇幅所限,这里只探讨了其中很少的几个特性。我强烈建议需要在软件开发项目中采用 XML 序列化和反序列化的开发人员考虑使用 Simple。