使用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是统一到一个默认的命名空间中了。
转:http://outofmemory.cn/java/jaxb/jaxb-and-java-util-map