使用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 addressMap = new HashMap();       public Map getAddressMap() {         return addressMap;     }       public void setAddressMap(Map 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"?>

shipping

2 B Road

billing

1 A Street

可以看到默认情况下map被序列化成一个一个的entry节点,每个entry节点都有key和value子节点。

修改map属性的节点名称

下面我们修改下addressMap节点的名字,需要使用 @XmlElementWrapper 注解,这个注解应该添加到getAddressMap方法上,如下修改后的Csutomer getAdressMap方法的代码:

@XmlElementWrapper(name = "addresses")     public Map getAddressMap() {         return addressMap;     }

这样修改之后的输出如下:<?xml  version="1.0" encoding="UTF-8" standalone="yes"?>

shipping

2 B Road

billing

1 A Street

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"?>

shipping

2 B Road

billing

1 A Street

从上面的输出可以看到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> {           public static class AdaptedMap {                   public List entry = new ArrayList();        }           public static class Entry {                   public String key;                   public Address value;         }       @Override     public Map unmarshal(AdaptedMap adaptedMap) throws Exception {         Map map = new HashMap();         for(Entry entry : adaptedMap.entry) {             map.put(entry.key, entry.value);         }         return map;     }       @Override     public AdaptedMap marshal(Map map) throws Exception {         AdaptedMap adaptedMap = new AdaptedMap();         for(Map.Entry 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 getAddressMap() {     return addressMap; }

这样序列化类型就都属于我们自己的包了,会有统一的命名空间,我们看下输出xml:

<?xml  version="1.0" encoding="UTF-8" standalone="yes"?>

shipping

2 B Road

billing

1 A Street

可以看到最后输出的xml是统一到一个默认的命名空间中了。