我正在使用RestEasy 2.2.2开发一个JAX-RS Web服务,以部署到Tomcat 7.该服务通过使用JAXB返回(应该返回)XML。返回的XML应该包含ConcurrentHashMap的表示,类似于在以下代码中使用它的方式:
@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection
{
@XmlElement(name="item")
private ConcurrentHashMap<String, Item> items;
public ItemCollection()
{
items = new ConcurrentHashMap<String, item>();
// fill the map
}
}
Item
类还包含需要序列化为XML的ConcurrentHashMap
。
这是资源类:
@Path("/items")
public class ItemResource
{
@GET
@Produces(MediaType.APPLICATION_XML)
public ItemCollection getAllItems()
{
// get itemManager
return itemManager.getItems(); // ItemManager holds an instance of ItemCollection
}
}
此代码运行,但生成的XML没有内容:
<items>
<item/>
</items>
我想要输出的内容如下:
<items>
<item id="...">
<data>...</data>
<otheritems>
<otheritem id="...">
<someotherdata>...</someotherdata>
</otheritem>
</otheritems>
</item>
<item id="...">
<data>...</data>
<otheritems>
<otheritem id="...">
<someotherdata>...</someotherdata>
</otheritem>
<otheritem id="...">
<someotherdata>...</someotherdata>
</otheritem>
<otheritem id="...">
<someotherdata>...</someotherdata>
</otheritem>
</otheritems>
</item>
</items>
我发现在内置功能不足的情况下需要MessageBodyWriter
实现。我试图提出一个MessageBodyWriter
实现来编组ConcurrentHashMap
,但到目前为止我还没有能够使它工作(即我可以调用代码,但它会因各种异常而停止)。
我似乎不太了解应该如何实现和使用MessageBodyWriter
(和MessageBodyReader
)接口。我有Bill Burke的“RESTful Java with JAX-RS”一书。它在帮助设计JAX-RS服务方面非常有用,但我在相关章节中找不到关于MessageBodyWriter
功能的足够详细信息。我的互联网搜索也没有产生任何可以指导我正确方向的东西。
如果有人能帮我弄清楚如何正确实现MessageBodyWriter
(和MessageBodyReader
)接口,我将不胜感激。我不知道我是否错过了注释,错误注释,或者我是否需要一种全新的方法。
提前感谢您的帮助。
修改
将代码修改为以下内容会让我在那里:
@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection
{
private ConcurrentHashMap<String, Item> items;
public ItemCollection()
{
items = new ConcurrentHashMap<String, item>();
// fill the map
}
@XmlElement(name="item")
public Collection<Item> getItems()
{
return items.values();
}
}
这会生成我需要的XML(上面包含的示例)。但是,这个代码在解组时不起作用。我得到以下异常:
java.lang.UnsupportedOperationException
java.util.AbstractCollection.add(AbstractCollection.java:221)
com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:290)
com.sun.xml.internal.bind.v2.runtime.reflect.Lister$CollectionLister.addToPack(Lister.java:254)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Scope.add(Scope.java:106)
com.sun.xml.internal.bind.v2.runtime.property.ArrayERProperty$ReceiverImpl.receive(ArrayERProperty.java:195)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext.endElement(UnmarshallingContext.java:507)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.endElement(SAXConnector.java:145)
com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:601)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2938)
com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:648)
com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:140)
com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:511)
com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:808)
com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:737)
com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:119)
com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1205)
com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:522)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(UnmarshallerImpl.java:200)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(UnmarshallerImpl.java:173)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:137)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:142)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:151)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(AbstractUnmarshallerImpl.java:169)
// ... the rest
我认为原因是缺少“正确的设置器”,因此unmarshaller不会尝试将项添加到Collection
中,但我不知道该设置器的外观如何。如果有人知道如何做到这一点,我将不胜感激。
提前致谢。
编辑2:
顺便说一下,我看到克里斯的回复,他建议使用@XmlJavaTypeAdapter
。我已经尝试了这个建议,它让我接近我需要的XML。但是,我使用@XmlJavaTypeAdapter获取的XML中有一个额外的级别(<class><items><item>
而不是<items><item>
---如我的示例中所示,我将ConcurrentHashMap
个实例作为成员变量一堂课)。我似乎也无法更改各个地图项的元素名称(它们总是被称为“项目”)。
这些不是大问题,我可以进行必要的修改,并在必要时与他们一起生活。但是,如果可能的话,我想首先没有它们。出于教育目的,我还想了解为什么编辑1中的代码不适用于解组(以及如果可能的话如何修复它)。
提前感谢所有帮助。
答案 0 :(得分:1)
我认为您的问题是您正在尝试将JAXB与MessageBodyReader / MessageBodyWriter混合使用。你有一个JAXB对象,所以你不希望RestEasy使用你的MessageBodyWriter进行所有的序列化,因为它不会考虑你的JAXB对象。你可以这样做,但你也需要序列化你的对象模型的其余部分。
MessageBodyReader / Writer适合使用Streams,这可能是它没有多大意义的原因。它不会假设你要使用XML。
您可能想要做的是为地图创建一个JAXB XmlJavaTypeAdapter,让JAXB进行XML创建。您可以在JavaDoc页面上找到更多信息:
我确实通过Metro邮件列表here找到了一篇好文章。此代码应该为您提供所需的内容。上下文围绕JAX-WS,但您正在寻找JAXB注释来进行自定义绑定。
@XmlRootElement
public class Root
{
private String name;
private int junk;
@XmlJavaTypeAdapter(MapAdapter.class)
private Map<String, Boolean> lights;
public Root()
{
name = ""; junk = 0; lights = new HashMap<String, Boolean>();
}
public void setName(String newName) { name = newName; }
public String getName() { return name; }
public void setJunk(int newJunk) { junk = newJunk; }
public int getJunk() { return junk; }
public void turnLightOn(String lightName) { lights.put(lightName, true); }
public void turnLightOff(String lightName) { lights.put(lightName, false); }
}
class MapAdapter extends XmlAdapter<MapElements[], Map<String, Boolean>>
{
public MapElements[] marshal(Map<String, Boolean> arg0) throws Exception
{
MapElements[] mapElements = new MapElements[arg0.size()];
int i = 0;
for (Map.Entry<String, Boolean> entry : arg0.entrySet())
mapElements[i++] = new MapElements(entry.getKey(), entry.getValue());
return mapElements;
}
public Map<String, Boolean> unmarshal(MapElements[] arg0) throws Exception
{
Map<String,Boolean> r = new HashMap<String,Boolean>();
for(MapElements mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
class MapElements
{
@XmlElement public String key;
@XmlElement public Boolean value;
private MapElements() {} //Required by JAXB
public MapElements(String key, Boolean value)
{
this.key = key;
this.value = value;
}
}
答案 1 :(得分:0)
XmlJavaTypeAdapter
方法工作正常,因为它允许JAXB进行序列化并返回。但是,它没有给我我需要的XML。我认为这不会太重要,但最后我发现我需要以这种形式获取XML。
我使用@XmlJavaTypeAdapter
获得的XML中有一个额外的级别(类似于<collection><mapitem><item><mapitem><item>
而不是<collection><item><item>
)。我在论坛上发现了一个帖子(如果我能再次找到它,我会添加链接)解释了这个问题,并指出为了获得这种类型的XML,JAXB需要查看集合,而不是地图。
所以,希望它对别人有用,这就是我所做的:
首先,我定义了这个界面:
public interface MyCollectionInterface
{
public String getItemId();
}
然后我修改了要放入集合中的项目,如下所示:
public class CollectionItem implements MyCollectionInterface
{
@XmlAttribute
private String id; // This was already a class member
@Override
public String getItemId()
{
return id;
}
}
我们的想法是让密钥与HashMap
一起使用。
然后是MyCollection
public class MyCollection<E extends MyCollectionInterface> extends AbstractSet<E>
{
private ConcurrentHashMap<String, E> items;
public MyCollection()
{
items = new ConcurrentHashMap<String, E>();
}
@Override
public boolean add(E e)
{
return items.putIfAbsent(e.getItemId(), e) != null;
}
@Override
public Iterator<E> iterator()
{
return items.values().iterator();
}
@Override
public int size()
{
return items.size();
}
// other functionality as needed
}
现在,将代码修改为以下形式可以完成所有工作:
@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection
{
@XmlElement(name="item")
private MyDictionary<CollectionItem> items;
public ItemCollection()
{
items = new MyDictionary<CollectionItem>();
}
}
这产生了我以后的XML(参见我的原始帖子中的一个例子)。