JAXB-4 Java 类与 XML 的转换
4 Java 类与 XML 的转换
通过前面几章的学习,我们已经掌握了,手动为 Java Bean 添加注解,与简单的 XML 进行相互转换;接下来,探讨多种关联关系在 JAXB 中的实现。
JAXB 支持 Java 对象树与 XML 文档相互转换,有如下几种场景:
- 简单 Java 类与 XML 的转换
- 一一关联的 Java 类与 XML 的转换
- 一对多(List)关联 Java 类与 XML 的转换
- 一对多(Map)关联 Java 类与 XML 的转换
4.1 简单 Java 类与 XML 的转换
简单 Java 类,指由基础数据类型(如 Integer, String, Boolean, Double 等)构建而成的 Java 类。
1、首先定义需要转换的 Java bean Student.java
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Student {
@XmlAttribute
private String id;
private String name;
private Integer age;
// getters, setters
}
这个对象和我们常见的Java对象唯一的不同,就是在普通的Java类上添加了注解 @XmlRootElement,默认类的属性(get/set)会被识别成 XML 节点元素,通过添加 @XmlAttribute 注解,将属性识别成该类节点的属性值。
2、进行简单 Java 类与 XML 的转换测试
public void test() throws JAXBException {
Student stu = new Student();
stu.setId("001");
stu.setName("Tom");
stu.setAge(22);
// Marshaller
JAXBContext context = JAXBContext.newInstance(Student.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
StringWriter sw = new StringWriter();
marshaller.marshal(stu, sw);
System.out.println(sw.toString());
// UnMarshaller
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(sw.toString());
Student stu2 = (Student) unmarshaller.unmarshal(sr);
}
这里演示了最基本的Java 对象和XML相互转换的过程。使用 JAXB 不需要引入第三方依赖jar包。在控制台可以看到如下输出。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<student id="001">
<age>22</age>
<name>Tom</name>
</student>
有几点需要注意:
- JAXB 转换对象必须属于JAXBElement类型,或者使用 @XmlRootElement注解
- JAXB 转换对象必须拥有无参数构造器(默认存在,如果被覆盖,需要显示指定)
4.2 一一关联的 Java 类与 XML 的转换
Order对象中包含Product对象,这在项目中是常见情形。Order 对象无需进行特殊的配置,JAXB 能够对 Order 对象嵌套的 Cashier 对象进行解析。
Order对象的第三个属性是个复杂的数据类型。
@XmlRootElement(name = "Order")
@XmlAccessorType(XmlAccessType.FIELD)
public class Order {
@XmlAttribute
private String id;
private Double price;
private Cashier cashier;
//setters, getters
}
被嵌套的 Cashier 不需要使用 @XmlRootElement注解。
@XmlAccessorType(XmlAccessType.FIELD)
public class Cashier {
@XmlAttribute
private String id;
private String name;
//setters, getters
}
编写测试单元进行测试;
public void test() throws JAXBException {
// Cashier
Cashier c = new Cashier();
c.setId("1100");
c.setName("Tom");
// Order
Order order = new Order();
order.setId("1101");
order.setPrice(23.45);
order.setCashier(c);
// Marshaller
JAXBContext context = JAXBContext.newInstance(Order.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter sw = new StringWriter();
marshaller.marshal(order, sw);
System.out.println(sw.toString());
// UnMarshaller
Unmarshaller unmarshaller = context.createUnmarshaller();
StringReader sr = new StringReader(sw.toString());
Order order2 = (Order) unmarshaller.unmarshal(sr);
}
生成的XML含有两个层级。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Order id="1101">
<cashier id="1100">
<name>Tom</name>
</cashier>
<price>23.45</price>
</Order>
4.3 一对多(List)关联 Java 类与 XML 的转换
Java 对象中含有 List,可以采取如下几种方式:
- 采用默认配置处理 List 集合
- 采用 @XmlElementWrapper 注解,将集合元素作为指定 XML 节点的次级元素;
- 采用 @XmlValue 注解,将非简单 Java 类转换为简单 XML 元素;
- 采用 @XmlList 注解,在一个 XML 的 Element 中添加多个值。
泛型 List 集合类型,可以使用基本数据类型,也可以使用自定义类型;在 XML 的转换中,基础数据类型,如 String, Integer, Double, Decimal 等,会被直接当做 XML 的节点内容,而自定义类型,会被按照对象树进行 XML 的转换。接下来,我们就这两种类型,来了解 JAXB 支持的 List 集合处理。
1、采用默认配置处理 List 集合
JABX默认支持 List 对象的解析,无需特别声明即可生成 List 对象的 XML。
处理基础数据类型的 List 集合
商品信息中的有很多小项,所以使用List类型。
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {
@XmlAttribute
private String id;
private List<String> item;
// setters,getters
}
测试一下。
@Test
public void test1() throws JAXBException {
Product product = new Product();
product.setId("1301");
product.setItem(Arrays.asList("ItemA","ItemB","ItemC"));
JAXB.marshal(product, System.out);
}
XML结果。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1301">
<item>ItemA</item>
<item>ItemB</item>
<item>ItemC</item>
</product>
这是最普通的一种转化方式。如果需要改变XML的Element的名称,可以设置@XmlElement(name = “Item”)。
处理类对象的 List 集合
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {
@XmlAttribute
private String id;
private List<Item> item;
// setters,getters
}
每一个小项都更加复杂。
@XmlAccessorType(XmlAccessType.FIELD)
public class Item {
@XmlAttribute
private String id;
// setters,getters
}
测试一下。
@Test
public void test() throws JAXBException {
Product product = new Product();
product.setId("1303");
product.setItem(Arrays.asList(
new Item("13031", "ItemA"),
new Item("13032", "ItemB"),
new Item("13033", "ItemC")));
JAXB.marshal(product, System.out);
}
XML结果。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1303">
<item id="13031">
<name>ItemA</name>
</item>
<item id="13032">
<name>ItemB</name>
</item>
<item id="13033">
<name>ItemC</name>
</item>
</product>
2、采用 @XmlElementWrapper 注解
处理基础数据类型的 List 集合
如果想让生成的XML外围被包裹起来,可以加上注解 @XmlElementWrapper。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {
@XmlAttribute
private String id;
@XmlElementWrapper(name = "Items")
private List<String> item;
// setters,getters
}
测试一下。
@Test
public void test() throws JAXBException {
Product product = new Product();
product.setId("1302");
product.setItem(Arrays.asList("ItemA","ItemB","ItemC"));
JAXB.marshal(product, System.out);
}
XML结果。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product2 id="1302">
<Items>
<item>ItemA</item>
<item>ItemB</item>
<item>ItemC</item>
</Items>
</product2>
可以看到,item 有了父标签Items
处理类对象的 List 集合
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {
@XmlAttribute
private String id;
@XmlElementWrapper(name = "Items")
private List<Item> item;
// setters,getters
}
@XmlAccessorType(XmlAccessType.FIELD)
public class Item {
@XmlAttribute
private String id;
// setters,getters
}
测试一下。
@Test
public void test() throws JAXBException {
Product product = new Product();
product.setId("1302");
product.setItem(Arrays.asList(
new Item("13031", "ItemA"),
new Item("13032", "ItemB"),
new Item("13033", "ItemC")));
JAXB.marshal(product, System.out);
}
XML结果。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1302">
<Items>
<item id="13031">
<name>ItemA</name>
</item>
<item id="13032">
<name>ItemB</name>
</item>
<item id="13033">
<name>ItemC</name>
</item>
</Items>
</product>
可以看到,item 有了父标签Items
3、采用 @XmlValue 注解,将非简单 Java 类转换为简单 XML 元素;
处理基础数据类型的 List 集合
注解 @XmlValue 只用于类对象。
处理类对象的 List 集合
商品信息中的小项还含有属性。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product {
@XmlAttribute
private String id;
private List<Item> item;
// setters,getters
}
每一个小项都更加复杂,注意这里的 name 使用的注解@XmlValue。
@XmlAccessorType(XmlAccessType.FIELD)
public class Item {
@XmlAttribute
private String id;
@XmlValue
private String name;
// setters,getters
}
测试一下。
@Test
public void test3() throws JAXBException {
Product product = new Product();
product.setId("1303");
product.setItem(Arrays.asList(new Item("13031","ItemA"),
new Item("13032","ItemB"),
new Item("13033","ItemC")));
JAXB.marshal(product, System.out);
}
生成的XML。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product id="1303">
<item id="13031">ItemA</item>
<item id="13032">ItemB</item>
<item id="13033">ItemC</item>
</product>
通过 @XmlValue 注解,将类对象的某一字段/属性标识为节点内容。
4、采用 @XmlList 注解,在一个 XML 的 Element 中添加多个值
处理基础数据类型的 List 集合
在JAXB 中,有一个注解 @XmlList主要是为了在一个XML的Element中添加多个值。
@XmlAccessorType(XmlAccessType.FIELD)
public class Product4 {
@XmlAttribute
private String id;
@XmlList
private List<String> item;
// setters,getters
}
测试一下。
@Test
public void test4() throws JAXBException {
Product4 product = new Product4();
product.setId("1304");
product.setItem(Arrays.asList("ItemA","ItemB","ItemC"));
JAXB.marshal(product, System.out);
}
生成的XMl 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product4 id="1304">
<item>ItemA ItemB ItemC</item>
</product4>
可以看到,item包含了List中的所有数据。
处理类对象的 List 集合
注解 @XmlList 与类对象不兼容。
4.4 一对多(Map)关联 Java 类与 XML 的转换
Map节点的名称变更
改造需要的代码量比较多,因为JAXB原生不指定Map的自定义操作。也可以说JAXB不支持Map这种数据类型。所以需要使用到适配器来扩展 JAXB 的功能。
首先定义一个类,其中只有两个字段,为了简单,可以不写setters/getters方法。通过这种方式模拟一个Map,只包含key/value,也就是first/second,这个名称就是XML的节点显示名称。
public class XmlMap {
public String first;
public String second;
}
自定义一个Adapter,这里将所有的代码都展示出来。
public class MapAdapter extends XmlAdapter<XmlMap[], Map<String, String>>{
@Override
public Map<String, String> unmarshal(XmlMap[] v) throws Exception {
Map<String, String> map = new HashMap<>();
for(int i=0; i<v.length; i++) {
XmlMap pairs = v[i];
map.put(pairs.first, pairs.second);
}
return map;
}
@Override
public XmlMap[] marshal(Map<String, String> v) throws Exception {
XmlMap[] xmlMap = new XmlMap[v.size()];
int index = 0;
for(Map.Entry<String, String> entry: v.entrySet()) {
XmlMap xm = new XmlMap();
xm.first = entry.getKey();
xm.second = entry.getValue();
xmlMap[index++] = xm;
}
return xmlMap;
}
}
@XmlJavaTypeAdapterJAXB能够内置支持List和Set集合,但是对于Map的支持需要自己处理。它继承自抽象类XmlAdapter<ValueType,BoundType> 类型参数:
- BoundType JAXB 不知道如何处理的一些类型。自定义的类型,告诉Jaxb ValueType 将此类型用作内存表示形式。
- ValueType JAXB 无需其他操作便知道如何处理的类型。
这里的Map对于JAXB是一个未知类型,但是XmlMap[]却是已知的对象数组类型。通过中间的转化赋值,可以使XmlMap[]与Map相互转化,从而让Jaxb知道数据如何处理。
在之前的Product中,在Map上加上注解@XmlJavaTypeAdapter(MapAdapter.class)。
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Product2 {
private String id;
@XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> category;
// setters, getters
}
测试一下:
@Test
public void test2() throws JAXBException {
Map<String, String> map = new HashMap<>();
map.put("衣服", "大衣");
map.put("裤子", "西裤");
Product2 product = new Product2();
product.setId("1402");
product.setCategory(map);
JAXB.marshal(product, System.out);
}
得到的结果:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<product2>
<id>1402</id>
<category>
<item>
<first>衣服</first>
<second>大衣</second>
</item>
<item>
<first>裤子</first>
<second>西裤</second>
</item>
</category>
</product2>
上面的所有节点名称除了item都是可以通过一定的方法改变的。
上一章:JAXB-3 JAXB API
目录:学习 JAXB
下一章:JAXB-5 动态 XML 生成