JAXB键值对编组:值支持嵌套的XMLAdapter实现方案咨询
解决Parameter类的XML多类型Value适配问题
先明确场景:我定义了一个Parameter类,用来映射XML中的键值节点,代码如下:
public class Parameter implements Serializable { @XmlElement(name = "Key") protected String key; @XmlElement(name = "Value") protected Object value; }
其中value字段支持两种类型:
- 字符串类型,此时
Parameter等价于Map<String, String>的键值对 - 另一个
Parameter对象(甚至是嵌套多层的Parameter列表)
对应的合法XML示例有两种:
单层级键值对
<Parameter> <Key>MyKey</Key> <Value>MyValue</Value> </Parameter>
嵌套多层的结构
<Parameter> <Key>Key1</Key> <Value> <Key>Key2</Key> <Value> <Key>Key3</Key> <Value>ValueInString1</Value> </Value> <Value> <Key>Key4</Key> <Value>ValueInString2</Value> </Value> </Value>
现在要实现XMLAdapter来处理这种多类型场景,之前的两种思路卡点可以这样解决:
思路2:仅为Value属性使用适配器(更推荐)
卡点是当value为Parameter对象时,无法调用通用编组器。解决方法是在适配器中获取JAXB上下文,直接复用Parameter的默认编组逻辑:
- 先给
value字段加上@XmlJavaTypeAdapter注解,指定适配器:
public class Parameter implements Serializable { @XmlElement(name = "Key") protected String key; @XmlElement(name = "Value") @XmlJavaTypeAdapter(ValueAdapter.class) protected Object value; }
- 实现
ValueAdapter,在适配逻辑中区分类型,遇到Parameter时用JAXB上下文来编组/解组:
public class ValueAdapter extends XmlAdapter<Object, Object> { private transient JAXBContext jaxbContext; private JAXBContext getJaxbContext() throws JAXBException { if (jaxbContext == null) { jaxbContext = JAXBContext.newInstance(Parameter.class); } return jaxbContext; } @Override public Object unmarshal(Object v) throws Exception { if (v instanceof Element) { Element element = (Element) v; if ("Parameter".equals(element.getNodeName())) { return getJaxbContext().createUnmarshaller().unmarshal(element); } } return v; } @Override public Object marshal(Object v) throws Exception { if (v instanceof Parameter) { Marshaller marshaller = getJaxbContext().createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true); StringWriter writer = new StringWriter(); marshaller.marshal(v, writer); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); return db.parse(new InputSource(new StringReader(writer.toString()))).getDocumentElement(); } return v; } }
这样就能自动区分value的类型,复用JAXB对Parameter的默认处理逻辑,不用手动写嵌套结构的编组代码。
思路1:为整个Parameter类使用适配器
卡点是编组键值对列表,解决方法是定义一个中间适配类,用来映射XML的结构,再在适配器中做Parameter和中间类的转换:
- 定义中间类
ParameterXml,用来对应XML中的结构:
public class ParameterXml implements Serializable { @XmlElement(name = "Key") private String key; @XmlElement(name = "Value") private List<Object> values; // 构造器、getter、setter省略 }
- 实现
ParameterAdapter,处理Parameter和ParameterXml的互转:
public class ParameterAdapter extends XmlAdapter<ParameterXml, Parameter> { @Override public Parameter unmarshal(ParameterXml xml) throws Exception { Parameter param = new Parameter(); param.setKey(xml.getKey()); if (xml.getValues() != null && !xml.getValues().isEmpty()) { if (xml.getValues().size() == 1) { Object value = xml.getValues().get(0); if (value instanceof ParameterXml) { param.setValue(unmarshal((ParameterXml) value)); } else { param.setValue(value); } } else { List<Parameter> subParams = new ArrayList<>(); for (Object val : xml.getValues()) { if (val instanceof ParameterXml) { subParams.add(unmarshal((ParameterXml) val)); } } param.setValue(subParams); } } return param; } @Override public ParameterXml marshal(Parameter param) throws Exception { ParameterXml xml = new ParameterXml(); xml.setKey(param.getKey()); List<Object> values = new ArrayList<>(); Object value = param.getValue(); if (value instanceof Parameter) { values.add(marshal((Parameter) value)); } else if (value instanceof List) { for (Object item : (List<?>) value) { if (item instanceof Parameter) { values.add(marshal((Parameter) item)); } } } else { values.add(value); } xml.setValues(values); return xml; } }
然后给Parameter类加上@XmlJavaTypeAdapter(ParameterAdapter.class)注解,就能完成整个类的适配。
两种思路对比:思路2更聚焦于问题核心(Value的多类型),代码更简洁,复用JAXB原生逻辑;思路1需要手动处理嵌套和列表结构,适合需要完全自定义XML结构的场景。
内容的提问来源于stack exchange,提问作者dtchmnt
相关产品推荐
相关产品推荐

