Java JAXB的通用映射
好的,所以我们都知道映射在JAXB中有点麻烦。 我在这里提出了当前解决方案的替代方案。我的主要目标是获得有关此解决方案的任何和所有潜在问题的反馈。也许出于某些原因,这甚至不是一个好的解决方案 当我使用标准时,似乎没有使用类的适配器。而是扫描类,迫使我用JAXB注释标记数据模型,并在不需要的地方添加默认构造函数(我说的是要存储在映射中的复杂类,而不是简单的数据类型)。最重要的是,这使我的内部数据模型公开,从而打破了封装,因为生成的XML是内部结构的直接表示 我所做的“变通方法”是将适配器与Java JAXB的通用映射,java,jaxb,Java,Jaxb,好的,所以我们都知道映射在JAXB中有点麻烦。 我在这里提出了当前解决方案的替代方案。我的主要目标是获得有关此解决方案的任何和所有潜在问题的反馈。也许出于某些原因,这甚至不是一个好的解决方案 当我使用标准时,似乎没有使用类的适配器。而是扫描类,迫使我用JAXB注释标记数据模型,并在不需要的地方添加默认构造函数(我说的是要存储在映射中的复杂类,而不是简单的数据类型)。最重要的是,这使我的内部数据模型公开,从而打破了封装,因为生成的XML是内部结构的直接表示 我所做的“变通方法”是将适配器与Mars
Marshall.Listener
和Unmarshall.Listner
结合起来,从而能够提取额外的注释信息。然后,将创建一个字段
@XmlElement(name = "testMap")
@XmlJavaTypeAdapter(MapAdapter.class)
@MapKeyValueAdapters(key=SomeComplexClassAdapter.class)
private final HashMap<SomeComplexClass, String> testMap2 = new HashMap<SomeComplexClass, String>();
这里是适配器
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;
}
}
这可能会产生类似的输出
<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>
java.util.HashMap
testMap2
1357未指定]]>
gn]]>
可以看出,密钥不需要资格信息,因为我们已经知道要使用的适配器。
还要注意,我将CDATA添加到输出中。我已经实现了一个简单的字符转义处理程序,它尊重这一点(不包括在这个代码示例中)
由于我们的发布周期,在我们的代码中实现这一功能的机会出现之前,我还有一段时间,因此我认为最好与社区一起检查这个解决方案是否存在任何问题,或者在我忽略的JAXB规范中是否有更好的方法。我还假设代码中有一些部分可以以更好的方式完成。
感谢您的评论。以下是我的解决方案:
@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);
}
}
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
公共静态类秩序{
@XmlID
字符串orderId;
字符串项;
整数计数;
}
@XmlRootElement(name=“客户”)
@XmlAccessorType(XmlAccessType.FIELD)
公共静态类客户{
字符串名;
字符串名;
@xmlementwrapper(name=“orders”)
@xmlement(name=“order”)
列表顺序=新的ArrayList();
@XmlTransient
private Map ordermap=新建LinkedHashMap();
/**
*重新初始化订单列表
*/
公共信息技术{
对于(订单:订单){
ordermap.put(order.orderId,order);
}
}
/**
*将给定的顺序添加到内部列表和映射中
*@param 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>
雌鹿
约翰
Id1
项目1
1.
Id2
项目2
2.
最小完整和可验证示例
例如
这看起来很复杂,并且以非常专业的方式实现。这是不可原谅的,因为需要一个通用的jaxb映射包装器来工作。对映射的支持应该在JAXB中提供,而不需要一些附加代码。我希望这个问题最终会在将来的JAXB版本中得到解决关于我提出的解决方案,它使用的是公共API,因此在JAXB中找到合适的解决方案之前,它应该是未来的证明。请注意,我尝试了自动化同步并基于XMLID注释重新进行同步,但结果再次复杂化,因此目前我不再费心了。您好,非常感谢您的回复。我正在尝试
unmarshal
XML,但我不知道如何实现Map
的unmarshal
方法。如果可能,请提供MapAdapter
类中提到的unmarshal
方法的示例:有关Java中XML支持的问题,请参阅。我已经转到python。
@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);
}
}
<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>