好的,所以我们都知道地图在JAXB中有点痛苦。 我在这里提出了当前解决方案的替代方案。我的主要目标是获得有关此解决方案的任何和所有潜在问题的反馈。也许由于某些原因它甚至不是一个好的解决方案。
当我使用标准Generic Map Adapter时,似乎没有使用类的适配器。这些类被扫描,迫使我用JAXB注释标记我的数据模型,并添加我不想要的默认构造函数(我在谈论我想要存储在地图中的复杂类,而不是简单的数据类型)。最重要的是,这使我的内部数据模型公开,从而破坏了封装,因为生成的XML是内部结构的直接表示。
"解决方法"我做的是将适配器与Marshall.Listener
和Unmarshall.Listner
组合,从而能够提取其他注释信息。那么一个字段就是
@XmlElement(name = "testMap")
@XmlJavaTypeAdapter(MapAdapter.class)
@MapKeyValueAdapters(key=SomeComplexClassAdapter.class)
private final HashMap<SomeComplexClass, String> testMap2 = new HashMap<SomeComplexClass, String>();
此附加注释接受key
和value
作为参数。如果省略,则功能将依赖于省略的标准限定。上面的示例将使用给定的适配器进行值的键和标准处理。
这里是注释。
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.xml.bind.annotation.adapters.XmlAdapter;
/**
* This annotation holds the adapters for the key and value used in the MapAdapter.
*/
@Retention(RUNTIME)
@Target({ FIELD })
public @interface MapKeyValueAdapters {
/**
* Points to the class that converts the value type to a bound type or vice versa. See {@link XmlAdapter} for more
* details.
*/
Class<? extends XmlAdapter<?, ?>> key() default UNDEFINED.class;
/**
* Points to the class that converts the value type to a bound type or vice versa. See {@link XmlAdapter} for more
* details.
*/
Class<? extends XmlAdapter<?, ?>> value() default UNDEFINED.class;
static final class UNDEFINED extends XmlAdapter<String, String> {
@Override
public String unmarshal(String v) throws Exception {
return null;
}
@Override
public String marshal(String v) throws Exception {
return null;
}
}
}
这里是适配器
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.IncompleteAnnotationException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBIntrospector;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.namespace.QName;
/**
* This class represents a general purpose Map adapter. It is capable of handling any type of class implementing the Map
* interface and has a no-args constructor.
*/
public class MapAdapter extends XmlAdapter<MapAdapter.Wrapper, Map<Object, Object>> {
private static final String XSI_NS = "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"";
private static final String XSI_TYPE = "xsi:type";
private static final String CDATA_START = "<![CDATA[";
private static final String CDATA_END = "]]>";
private final MarshallerListener marshallerListener = new MarshallerListener();
private final UnmarshallerListener unmarshallerListener = new UnmarshallerListener();
private final JAXBContext context;
public MapAdapter(JAXBContext inContext) {
context = inContext;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Map<Object, Object> unmarshal(Wrapper inWrapper) throws Exception {
if (inWrapper == null) {
return null;
}
Info info = null;
for (Info element : unmarshallerListener.infoList) {
if (element.field.equals(inWrapper.field)) {
info = element;
}
}
if (info != null) {
Class<Map<Object, Object>> clazz = (Class<Map<Object, Object>>) Class.forName(inWrapper.mapClass);
Map<Object, Object> outMap = clazz.newInstance();
XmlAdapter<Object, Object> keyAdapter = null;
XmlAdapter<Object, Object> valueAdapter = null;
if (info.adapters.key() != MapKeyValueAdapters.UNDEFINED.class) {
keyAdapter = (XmlAdapter<Object, Object>) info.adapters.key().getConstructor().newInstance();
}
if (info.adapters.value() != MapKeyValueAdapters.UNDEFINED.class) {
valueAdapter = (XmlAdapter<Object, Object>) info.adapters.value().getConstructor().newInstance();
}
Unmarshaller um = context.createUnmarshaller();
for (MapEntry entry : inWrapper.mapList) {
Object key = ((JAXBElement) um.unmarshal(new StringReader(entry.key))).getValue();
if (keyAdapter != null) {
key = keyAdapter.unmarshal(key);
}
Object value = ((JAXBElement) um.unmarshal(new StringReader(entry.value))).getValue();
if (valueAdapter != null) {
value = valueAdapter.unmarshal(value);
}
outMap.put(key, value);
}
return outMap;
} else {
throw new IllegalStateException("Adapter info could not be found.");
}
}
@SuppressWarnings("unchecked")
@Override
public Wrapper marshal(Map<Object, Object> inMap) throws Exception {
if (inMap == null) {
return null;
}
Info info = null;
for (Info element : marshallerListener.infoList) {
if (element.map == inMap) {
info = element;
}
}
if (info != null) {
Wrapper outWrapper = new Wrapper();
outWrapper.mapClass = inMap.getClass().getName();
outWrapper.field = info.field;
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
JAXBIntrospector introspector = context.createJAXBIntrospector();
XmlAdapter<Object, Object> keyAdapter = null;
XmlAdapter<Object, Object> valueAdapter = null;
if (info.adapters.key() != MapKeyValueAdapters.UNDEFINED.class) {
keyAdapter = (XmlAdapter<Object, Object>) info.adapters.key().getConstructor().newInstance();
}
if (info.adapters.value() != MapKeyValueAdapters.UNDEFINED.class) {
valueAdapter = (XmlAdapter<Object, Object>) info.adapters.value().getConstructor().newInstance();
}
for (Map.Entry<?, ?> entry : inMap.entrySet()) {
MapEntry jaxbEntry = new MapEntry();
outWrapper.mapList.add(jaxbEntry);
Object key = entry.getKey();
if (key != null) {
Class<Object> clazz = Object.class;
if (keyAdapter != null) {
key = keyAdapter.marshal(key);
clazz = (Class<Object>) key.getClass();
}
if (introspector.getElementName(key) == null) {
// The value of clazz determines if the qualification is written or not; Object.class generates the
// qualification.
key = new JAXBElement<Object>(new QName("key"), clazz, key);
}
StringWriter writer = new StringWriter();
m.marshal(key, writer);
jaxbEntry.key = format("key", writer.toString());
}
Object value = entry.getValue();
if (value != null) {
Class<Object> clazz = Object.class;
if (valueAdapter != null) {
value = valueAdapter.marshal(value);
clazz = (Class<Object>) value.getClass();
}
if (introspector.getElementName(value) == null) {
// The value of clazz determines if the qualification is written or not; Object.class generates the
// qualification.
value = new JAXBElement<Object>(new QName("value"), clazz, value);
}
StringWriter writer = new StringWriter();
m.marshal(value, writer);
jaxbEntry.value = format("value", writer.toString());
}
}
return outWrapper;
} else {
throw new IllegalStateException("Adapter info could not be found.");
}
}
private String format(String inTagName, String inXML) {
String element = "<" + inTagName;
// Remove unneeded namespaces, they are already declared in the top node.
int beginIndex = inXML.indexOf(XSI_TYPE);
if (beginIndex != -1) {
int endIndex = inXML.indexOf(" ", beginIndex);
element += " " + inXML.substring(beginIndex, endIndex) + " " + XSI_NS;
}
beginIndex = inXML.indexOf('>');
element += inXML.substring(beginIndex);
return CDATA_START + element + CDATA_END;
}
@XmlType(name = "map")
static class Wrapper {
@XmlElement(name = "mapClass")
private String mapClass;
@XmlElement(name = "field")
private String field;
@XmlElementWrapper(name = "map")
@XmlElement(name = "entry")
private final List<MapEntry> mapList = new ArrayList<MapEntry>();
}
@XmlType(name = "mapEntry")
static class MapEntry {
@XmlElement(name = "key")
private String key;
@XmlElement(name = "value")
private String value;
}
public Marshaller.Listener getMarshallerListener() {
return marshallerListener;
}
public Unmarshaller.Listener getUnmarshallerListener() {
return unmarshallerListener;
}
private static class MarshallerListener extends Marshaller.Listener {
private final List<Info> infoList = new ArrayList<Info>();
@Override
public void beforeMarshal(Object inSource) {
extractInfo(infoList, inSource);
}
}
private class UnmarshallerListener extends Unmarshaller.Listener {
private final List<Info> infoList = new ArrayList<Info>();
@Override
public void beforeUnmarshal(Object inTarget, Object inParent) {
extractInfo(infoList, inTarget);
}
}
private static void extractInfo(List<Info> inList, Object inObject) {
for (Field field : inObject.getClass().getDeclaredFields()) {
for (Annotation a : field.getAnnotations()) {
if (a.annotationType() == XmlJavaTypeAdapter.class) {
if (((XmlJavaTypeAdapter) a).value() == MapAdapter.class) {
MapKeyValueAdapters adapters = field.getAnnotation(MapKeyValueAdapters.class);
if (adapters == null) {
throw new IncompleteAnnotationException(XmlJavaTypeAdapter.class, "; XmlJavaTypeAdapter specifies "
+ MapAdapter.class.getName() + " for field " + field.getName() + " in "
+ inObject.getClass().getName() + ". This must be used in combination with annotation "
+ MapKeyValueAdapters.class.getName());
}
try {
field.setAccessible(true);
Map<?, ?> value = (Map<?, ?>) field.get(inObject);
if (value != null) {
Info info = new Info();
info.field = field.getName();
info.map = value;
info.adapters = adapters;
inList.add(info);
}
} catch (Exception e) {
throw new RuntimeException("Failed extracting annotation information from " + field.getName() + " in "
+ inObject.getClass().getName(), e);
}
}
}
}
}
}
private static class Info {
private String field;
private Map<?, ?> map;
private MapKeyValueAdapters adapters;
}
}
请注意,只要适配器具有默认构造函数,该适配器就能够处理所有类型的地图。
最后设置适配器用法的代码。
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
/**
* Singleton that manages the JAXB functionality.
*/
public enum JAXBManager {
INSTANCE;
private JAXBContext context;
private JAXBManager() {
try {
context = JAXBContext.newInstance(SomeComplexClass.class.getPackage().getName());
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
public Marshaller createMarshaller() throws JAXBException {
Marshaller m = context.createMarshaller();
MapAdapter adapter = new MapAdapter(context);
m.setAdapter(adapter);
m.setListener(adapter.getMarshallerListener());
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
return m;
}
public Unmarshaller createUnmarshaller() throws JAXBException {
Unmarshaller um = context.createUnmarshaller();
MapAdapter adapter = new MapAdapter(context);
um.setAdapter(adapter);
um.setListener(adapter.getUnmarshallerListener());
return um;
}
}
这可能会产生类似
的输出<testMap2>
<mapClass>java.util.HashMap</mapClass>
<field>testMap2</field>
<map>
<entry>
<key><![CDATA[<key><number>1357</number><type>Unspecified</type></key>]]></key>
<value><![CDATA[<value xsi:type="xs:string" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">gn</value>]]></value>
</entry>
</map>
</testMap2>
可以看出,密钥不需要资格信息,因为我们已经知道要使用的适配器。 另请注意,我将CDATA添加到输出中。我已经实现了一个尊重这个的简单字符转义处理程序(不包含在这个代码示例中)。
由于我们的发布周期,我在机会开放之前有一些时间在我们的代码中实现此功能,因此我认为,如果此解决方案存在任何问题或者是否存在任何问题,请与社区联系是明智的。我忽略了JAXB规范中已有的更好的方法。我还假设代码的某些部分可以用更好的方式完成。 感谢您的评论。
答案 0 :(得分:2)
以下是我的解决方法建议:
带有订单地图的示例客户
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public static class Order {
@XmlID
String orderId;
String item;
int count;
}
@XmlRootElement(name = "customer")
@XmlAccessorType(XmlAccessType.FIELD)
public static class Customer {
String name;
String firstname;
@XmlElementWrapper(name = "orders")
@XmlElement(name = "order")
List<Order> orders = new ArrayList<Order>();
@XmlTransient
private Map<String, Order> ordermap = new LinkedHashMap<String, Order>();
/**
* reinitialize the order list
*/
public void reinit() {
for (Order order : orders) {
ordermap.put(order.orderId, order);
}
}
/**
* add the given order to the internal list and map
* @param order - the order to add
*/
public void addOrder(Order order) {
orders.add(order);
ordermap.put(order.orderId,order);
}
}
示例XML
<customer>
<name>Doe</name>
<firstname>John</firstname>
<orders>
<order>
<orderId>Id1</orderId>
<item>Item 1</item>
<count>1</count>
</order>
<order>
<orderId>Id2</orderId>
<item>Item 2</item>
<count>2</count>
</order>
</orders>
</customer>
最小的完整且可验证的示例
根据
的例子