使用JAXB序列化java.util.Map接口可能会遇到一些问题,本文通过几种方式来做map的序列化,包括不做任何处理的序列化、修改节点名称、添加xml命名空间、使用XmlAdapter统一命名空间。
首先介绍下序列化涉及到的几个类:
Customer类包含一个Map的属性,Map的key类型是String类型,而value类型是我们自定义的POJO类型。其代码如下:
package cn.outofmemory.jaxb; import java.util.*; import javax.xml.bind.annotation.*; @XmlRootElement public class Customer { private Map<String, Address> addressMap = new HashMap<String, Address>(); public Map<String, Address> getAddressMap() { return addressMap; } public void setAddressMap(Map<String, Address> addressMap) { this.addressMap = addressMap; } }
Adress类是一个纯POJO类,定义如下
package cn.outofmemory.jaxb; public class Address { private String street; public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } }
序列化入口类,此类初始化了Customer对象,并将此对象的jaxb序列化结果,输出到System.out流中,代码如下:
package cn.outofmemory.jaxb; import javax.xml.bind.*; public class Demo { public static void main(String[] args) throws Exception { JAXBContext jc = JAXBContext.newInstance(Customer.class); Address billingAddress = new Address(); billingAddress.setStreet("1 A Street"); Address shippingAddress = new Address(); shippingAddress.setStreet("2 B Road"); Customer customer = new Customer(); customer.getAddressMap().put("billing", billingAddress); customer.getAddressMap().put("shipping", shippingAddress); Marshaller marshaller = jc.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(customer, System.out); } }
默认的jaxb序列化
下面我们看下不做任何设置的默认序列化结果:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customer> <addressMap> <entry> <key>shipping</key> <value> <street>2 B Road</street> </value> </entry> <entry> <key>billing</key> <value> <street>1 A Street</street> </value> </entry> </addressMap> </customer>
可以看到默认情况下map被序列化成一个一个的entry节点,每个entry节点都有key和value子节点。
修改map属性的节点名称
下面我们修改下addressMap节点的名字,需要使用 @XmlElementWrapper 注解,这个注解应该添加到getAddressMap方法上,如下修改后的Csutomer getAdressMap方法的代码:
@XmlElementWrapper(name = "addresses") public Map<String, Address> getAddressMap() { return addressMap; }
这样修改之后的输出如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customer> <addresses> <entry> <key>shipping</key> <value> <street>2 B Road</street> </value> </entry> <entry> <key>billing</key> <value> <street>1 A Street</street> </value> </entry> </addresses> </customer>
jaxb添加xml命名空间
下面我们再看下如何使用JAXB控制xml的namespace,我们需要在package-info.java文件中给package添加如下注解:
@XmlSchema( namespace="http://outofmemory.cn", elementFormDefault=XmlNsForm.QUALIFIED) package cn.outofmemory.jaxb; import javax.xml.bind.annotation.*;
XmlSchema注解指定了包中jaxb序列化的命名空间。
我们可以再运行下Demo看下添加命名空间之后的输出:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:customer xmlns:ns2="http://outofmemory.cn"> <ns2:addresses> <entry> <key>shipping</key> <value> <ns2:street>2 B Road</ns2:street> </value> </entry> <entry> <key>billing</key> <value> <ns2:street>1 A Street</ns2:street> </value> </entry> </ns2:addresses> </ns2:customer>
从上面的输出可以看到Customer类和Adress类的节点和属性节点都添加了ns2的命名空间限定,而Map类相关的都没有添加命名空间限定,这是因为Map属于java.util包,这个包中没有命名空间限定的注解修饰。
使用XmlAdapter统一jaxb序列化后的xml命名空间
下面我们通过XmlAdapter类实现统一的命名空间限定。
使用XmlAdapter可以转换指定属性的jaxb序列化方式,我们自定义的XmlAdapter属于我们自己所创建的包,而在这个包上是有命名空间注解修饰的。
package cn.outofmemory.jaxb; import java.util.*; import javax.xml.bind.annotation.adapters.XmlAdapter; public class MapAdapter extends XmlAdapter<MapAdapter.AdaptedMap, Map<String, Address>> { public static class AdaptedMap { public List<Entry> entry = new ArrayList<Entry>(); } public static class Entry { public String key; public Address value; } @Override public Map<String, Address> unmarshal(AdaptedMap adaptedMap) throws Exception { Map<String, Address> map = new HashMap<String, Address>(); for(Entry entry : adaptedMap.entry) { map.put(entry.key, entry.value); } return map; } @Override public AdaptedMap marshal(Map<String, Address> map) throws Exception { AdaptedMap adaptedMap = new AdaptedMap(); for(Map.Entry<String, Address> mapEntry : map.entrySet()) { Entry entry = new Entry(); entry.key = mapEntry.getKey(); entry.value = mapEntry.getValue(); adaptedMap.entry.add(entry); } return adaptedMap; } }
XmlAdapter是一个泛型的抽象类,我们需要自己实现marshal和unmarshal方法。在我们的自定义XmlAdapter中我们将Map的键值对转换成我们自定义的Entry类实例,而Entry类所在包是有命名空间注解修饰的。
然后需要将MapAdapter通过XmlJavaTypeAdapter注解添加到Customer的getAddressMap()方法上,如下代码示例:
@XmlJavaTypeAdapter(MapAdapter.class) @XmlElement(name="addresses") public Map<String, Address> getAddressMap() { return addressMap; }
这样序列化类型就都属于我们自己的包了,会有统一的命名空间,我们看下输出xml:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <customer xmlns="http://outofmemory.cn"> <addresses> <entry> <key>shipping</key> <value> <street>2 B Road</street> </value> </entry> <entry> <key>billing</key> <value> <street>1 A Street</street> </value> </entry> </addresses> </customer>
可以看到最后输出的xml是统一到一个默认的命名空间中了。