使用JAXB的动态标记名称

我正在使用Jersey和JAXB来构建一个简单的RESTful Web服务,我有一个’String’的HashMap到’Integer’:

2010-04 -> 24 2010-05 -> 45 

我需要生成一个XML响应,如下所示:

   24 45  

使用JAXB生成动态标记名称的最佳方法是什么?

您可以使用@XmlAnyElement -annotated属性并将元素作为JAXBElement返回:

 private Map months = ...; @XmlAnyElement public List> getMonths() { List> elements = new ArrayList>(); for (Map.Entry month: months.entrySet()) elements.add(new JAXBElement(new QName(month.getKey()), Integer.class, month.getValue())); return elements; } 

这种方法很丑陋,但并不比它产生的XML更丑陋。

最近也遇到了这种问题。 在引用上面列出的axtavt的答案(以及一堆其他问题线程)后,我对这类问题做了总结:

  1. 一个容器类,它包含JAXBElement对象的列表(或数组),其中此列表(或数组)使用@XmlAnyElement注释,因此可以生成动态元素名称。
  2. 一个XmlAdapter类,用于处理Map与/或此容器类之间的编组/解组。
  3. 使用@XmlJavaTypeAdapter注释java bean的任何Map字段,并将此XmlAdapter类作为其值(或者您可以直接使用容器类,如下所示)。

现在我将把Map作为例子,在这里

 {"key1": "value1", "key2": "value2"} 

将被整理进去

  value1 value2  

以下是完整的代码段和注释,以及示例:

1,容器(用于@XmlAnyElement)

 /** * 
*
References: *
*
* *
*
* @author MEC * */ @XmlType public static class MapWrapper{ private List> properties = new ArrayList<>(); public MapWrapper(){ } /** *

* Funny fact: due to type erasure, this method may return * List instead of List> in the end; *

*

WARNING: do not use this method in your programme

*

* Thus to retrieve map entries you've stored in this MapWrapper, it's * recommended to use {@link #toMap()} instead. *

* @return */ @XmlAnyElement public List> getProperties() { return properties; } public void setProperties(List> properties) { this.properties = properties; } /** *

* Only use {@link #addEntry(JAXBElement)} and {{@link #addEntry(String, String)} * when this MapWrapper instance is created by yourself * (instead of through unmarshalling). *

* @param key map key * @param value map value */ public void addEntry(String key, String value){ JAXBElement
prop = new JAXBElement(new QName(key), String.class, value); addEntry(prop); } public void addEntry(JAXBElement prop){ properties.add(prop); } @Override public String toString() { return "MapWrapper [properties=" + toMap() + "]"; } /** *

* To Read-Only Map *

* * @return */ public Map toMap(){ //Note: Due to type erasure, you cannot use properties.stream() directly when unmashalling is used.. List props = properties; return props.stream().collect(Collectors.toMap(MapWrapper::extractLocalName, MapWrapper::extractTextContent)); } /** *

* Extract local name from obj, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element; *

* @param obj * @return */ @SuppressWarnings("unchecked") private static String extractLocalName(Object obj){ Map, Function> strFuncs = new HashMap<>(); strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement
)jaxb).getName().getLocalPart()); strFuncs.put(Element.class, ele -> ((Element) ele).getLocalName()); return extractPart(obj, strFuncs).orElse(""); } /** *

* Extract text content from obj, whether it's javax.xml.bind.JAXBElement or org.w3c.dom.Element; *

* @param obj * @return */ @SuppressWarnings("unchecked") private static String extractTextContent(Object obj){ Map, Function> strFuncs = new HashMap<>(); strFuncs.put(JAXBElement.class, (jaxb) -> ((JAXBElement
)jaxb).getValue()); strFuncs.put(Element.class, ele -> ((Element) ele).getTextContent()); return extractPart(obj, strFuncs).orElse(""); } /** * Check class type of obj according to types listed in strFuncs keys, * then extract some string part from it according to the extract function specified in strFuncs * values. * @param obj * @param strFuncs * @return */ private static Optional extractPart(ObjType obj, Map, Function> strFuncs){ for(Class clazz : strFuncs.keySet()){ if(clazz.isInstance(obj)){ return Optional.of(strFuncs.get(clazz).apply(obj)); } } return Optional.empty(); } }

笔记:

  1. 对于JAXB绑定,您需要注意的是此getProperties方法,该方法由@XmlAnyElement注释。
  2. 这里引入了两个addEntry方法,以方便使用。 应该谨慎使用它们,因为当它们通过JAXBContext用于新编组的MapWrapper (而不是由你自己通过一个new运算符创建),可能会发现可怕的错误。
  3. 这里介绍toMap用于信息探测,即帮助检查存储在此MapWrapper实例中的映射条目。

2,适配器(XmlAdapter)

XmlAdapter@XmlJavaTypeAdapter成对使用,在这种情况下,仅当Map用作bean属性时才需要它。

 /** * 

* ref: http://stackoverflow.com/questions/21382202/use-jaxb-xmlanyelement-type-of-style-to-return-dynamic-element-names *

* @author MEC * */ public static class MapAdapter extends XmlAdapter>{ @Override public Map unmarshal(MapWrapper v) throws Exception { Map map = v.toMap(); return map; } @Override public MapWrapper marshal(Map m) throws Exception { MapWrapper wrapper = new MapWrapper(); for(Map.Entry entry : m.entrySet()){ wrapper.addEntry(new JAXBElement(new QName(entry.getKey()), String.class, entry.getValue())); } return wrapper; } }

3,例子

以下是显示容器和适配器用法的两个示例。

3.1例1

要映射这个xml:

  value1 value2  

您可以使用以下类:

 @XmlRootElement(name="root") public class CustomMap extends MapWrapper{ public CustomMap(){ } } 

测试代码:

 CustomMap map = new CustomMap(); map.addEntry("key1", "value1"); map.addEntry("key1", "value2"); StringWriter sb = new StringWriter(); JAXBContext.newInstance(CustomMap.class).createMarshaller().marshal(map, sb); out.println(sb.toString()); 

请注意,此处不使用@XmlJavaTypeAdapter

3.2例2

要映射这个xml:

   value1 value2  other content  

您可以使用以下类:

 @XmlRootElement(name="root") @XmlType(propOrder={"map", "other"}) public class YetAnotherBean{ private Map map = new HashMap<>(); private String other; public YetAnotherBean(){ } public void putEntry(String key, String value){ map.put(key, value); } @XmlElement(name="map") @XmlJavaTypeAdapter(MapAdapter.class) public Map getMap(){ return map; } public void setMap(Map map){ this.map = map; } @XmlElement(name="other") public String getOther(){ return other; } public void setOther(String other){ this.other = other; } } 

测试代码:

 YetAnotherBean yab = new YetAnotherBean(); yab.putEntry("key1", "value1"); yab.putEntry("key2", "value2"); yab.setOther("other content"); StringWriter sb = new StringWriter(); JAXBContext.newInstance(YetAnotherBean.class).createMarshaller().marshal(yab, sb); out.println(sb.toString()); 

请注意, @XmlJavaTypeAdapter应用于Map字段,其中MapAdapter作为其值。

3.3例3

现在让我们为这些元素添加一些属性。 由于一些神秘的原因,我有这种XML结构来映射:

   SYSTEM DB FALSE   

如您所见,系统参数名称都设置为元素的名称而不是其属性。 要解决此问题,我们可以再次使用JAXBElement的一些帮助:

 @XmlRootElement(name="sys-config") public class SysParamConfigXDO{ private SysParamEntries sysParams = new SysParamEntries(); public SysParamConfigXDO(){ } public void addSysParam(String name, String value, String attr, String desc){ sysParams.addEntry(name, value, attr, desc);; } @XmlElement(name="sys-params") @XmlJavaTypeAdapter(SysParamEntriesAdapter.class) public SysParamEntries getSysParams() { return sysParams; } public void setSysParams(SysParamEntries sysParams) { this.sysParams = sysParams; } @Override public String toString() { return "SysParamConfigXDO [sysParams=" + sysParams + "]"; } } @XmlRootElement(name="root") public class SysParamXDO extends SysParamEntriesWrapper{ public SysParamXDO(){ } } @SuppressWarnings("unchecked") @XmlType public class SysParamEntriesWrapper{ /** * 

* Here is the tricky part: *

    *
  • When this SysParamEntriesWrapper is created by yourself, objects * stored in this entries list is of type SystemParamEntry
  • *
  • Yet during the unmarshalling process, this SysParamEntriesWrapper is * created by the JAXBContext, thus objects stored in the entries is * of type Element actually.
  • *
*

*/ List> entries = new ArrayList<>(); public SysParamEntriesWrapper(){ } public void addEntry(String name, String value, String attr, String desc){ addEntry(new SysParamEntry(name, value, attr, desc)); } public void addEntry(String name, String value){ addEntry(new SysParamEntry(name, value)); } public void addEntry(SysParamEntry entry){ JAXBElement bean = new JAXBElement(new QName("", entry.getName()), SysParamEntry.class, entry); entries.add(bean); } @XmlAnyElement public List> getEntries() { return entries; } public void setEntries(List> entries) { this.entries = entries; } @Override public String toString() { return "SysParammEntriesWrapper [entries=" + toMap() + "]"; } public Map toMap(){ Map retval = new HashMap<>(); List entries = this.entries; entries.stream().map(SysParamEntriesWrapper::convertToParamEntry). forEach(entry -> retval.put(entry.getName(), entry));; return retval; } private static SysParamEntry convertToParamEntry(Object entry){ String name = extractName(entry); String attr = extractAttr(entry); String desc = extractDesc(entry); String value = extractValue(entry); return new SysParamEntry(name, value, attr, desc); } @SuppressWarnings("unchecked") private static String extractName(Object entry){ return extractPart(entry, nameExtractors).orElse(""); } @SuppressWarnings("unchecked") private static String extractAttr(Object entry){ return extractPart(entry, attrExtractors).orElse(""); } @SuppressWarnings("unchecked") private static String extractDesc(Object entry){ return extractPart(entry, descExtractors).orElse(""); } @SuppressWarnings("unchecked") private static String extractValue(Object entry){ return extractPart(entry, valueExtractors).orElse(""); } private static Optional extractPart(ObjType obj, Map, Function> extractFuncs ){ for(Class clazz : extractFuncs.keySet()){ if(clazz.isInstance(obj)){ return Optional.ofNullable(extractFuncs.get(clazz).apply(obj)); } } return Optional.empty(); } private static Map, Function> nameExtractors = new HashMap<>(); private static Map, Function> attrExtractors = new HashMap<>(); private static Map, Function> descExtractors = new HashMap<>(); private static Map, Function> valueExtractors = new HashMap<>(); static{ nameExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getName().getLocalPart()); nameExtractors.put(Element.class, ele -> ((Element) ele).getLocalName()); attrExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getValue().getAttr()); attrExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("attr")); descExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getValue().getDesc()); descExtractors.put(Element.class, ele -> ((Element) ele).getAttribute("desc")); valueExtractors.put(JAXBElement.class, jaxb -> ((JAXBElement)jaxb).getValue().getValue()); valueExtractors.put(Element.class, ele -> ((Element) ele).getTextContent()); } } public class SysParamEntriesAdapter extends XmlAdapter{ @Override public SysParamEntries unmarshal(SysParamEntriesWrapper v) throws Exception { SysParamEntries retval = new SysParamEntries(); v.toMap().values().stream().forEach(retval::addEntry); return retval; } @Override public SysParamEntriesWrapper marshal(SysParamEntries v) throws Exception { SysParamEntriesWrapper entriesWrapper = new SysParamEntriesWrapper(); v.getEntries().forEach(entriesWrapper::addEntry); return entriesWrapper; } } public class SysParamEntries{ List entries = new ArrayList<>();; public SysParamEntries(){ } public SysParamEntries(List entries) { super(); this.entries = entries; } public void addEntry(SysParamEntry entry){ entries.add(entry); } public void addEntry(String name, String value){ addEntry(name, value, "C"); } public void addEntry(String name, String value, String attr){ addEntry(name, value, attr, ""); } public void addEntry(String name, String value, String attr, String desc){ entries.add(new SysParamEntry(name, value, attr, desc)); } public List getEntries() { return entries; } public void setEntries(List entries) { this.entries = entries; } @Override public String toString() { return "SystemParamEntries [entries=" + entries + "]"; } } @XmlType public class SysParamEntry{ String name; String value = ""; String attr = ""; String desc = ""; public SysParamEntry(){ } public SysParamEntry(String name, String value) { super(); this.name = name; this.value = value; } public SysParamEntry(String name, String value, String attr) { super(); this.name = name; this.value = value; this.attr = attr; } public SysParamEntry(String name, String value, String attr, String desc) { super(); this.name = name; this.value = value; this.attr = attr; this.desc = desc; } @XmlTransient public String getName() { return name; } public void setName(String name) { this.name = name; } @XmlValue public String getValue() { return value; } public void setValue(String value) { this.value = value; } @XmlAttribute(name="attr") public String getAttr() { return attr; } public void setAttr(String attr) { this.attr = attr; } @XmlAttribute(name="desc") public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } @Override public String toString() { return "SystemParamEntry [name=" + name + ", value=" + value + ", attr=" + attr + ", desc=" + desc + "]"; } }

现在是测试的时候了:

 //Marshal SysParamConfigXDO xdo = new SysParamConfigXDO(); xdo.addSysParam("ACCESSLOG_FILE_BY", "SYSTEM", "C", "AccessLog file desc"); xdo.addSysParam("ACCESSLOG_WRITE_MODE", "DB", "D", ""); xdo.addSysParam("CHANEG_BUTTON_IMAGES", "FALSE", "E", "Button Image URL, eh, boolean value. ...Wait, what?"); JAXBContext jaxbCtx = JAXBContext.newInstance(SysParamConfigXDO.class, SysParamEntries.class); jaxbCtx.createMarshaller().marshal(xdo, System.out); //Unmarshal Path xmlFile = Paths.get("path_to_the_saved_xml_file.xml"); JAXBContext jaxbCtx = JAXBContext.newInstance(SysParamConfigXDO.class, SysParamEntries.class); SysParamConfigXDO xdo = (SysParamConfigXDO) jaxbCtx.createUnmarshaller().unmarshal(xmlFile.toFile()); out.println(xdo.toString());