[英]How to get MessageBodyWriter to work with a HashMap using RestEasy and Tomcat?
我正在使用RestEasy 2.2.2開發一個JAX-RS Web服務,以將其部署到Tomcat7。該服務通過使用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
類還包含一個ConcurrentHashMap
,需要將其序列化為XML。
這是資源類:
@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
我認為原因是缺少“適當的設置器”,因此解組器不會嘗試將項目添加到Collection
但是我不知道該設置器的外觀。 如果有人知道該怎么做,我將不勝感激。
提前致謝。
編輯2:
順便說一句,我已經看到克里斯的答復,他建議使用@XmlJavaTypeAdapter
。 我嘗試過該建議,它使我接近所需的XML。 但是,我使用@XmlJavaTypeAdapter獲得的XML中有一個附加級別( <class><items><item>
而不是<items><item>
---如我的示例所示,我將ConcurrentHashMap
實例作為成員一類的變量)。 我似乎也無法更改各個地圖項的元素名稱(它們始終稱為“ item”)。
這些不是大問題,我可以進行必要的更改,並在必要時與他們一起生活。 但是,如果可能的話,我不想首先使用它們。 出於教育目的,我還想了解為什么EDIT 1中的代碼無法用於解組(以及如何修復它,如果可能的話)。
在此先感謝您提供的所有幫助。
我認為您的問題是您正在嘗試將JAXB與您的MessageBodyReader / MessageBodyWriter混合使用。 您有一個JAXB對象,因此您真的不希望RestEasy使用MessageBodyWriter進行所有序列化,因為它不會考慮您的JAXB對象。 您可以通過這種方式進行操作,但是您還需要序列化對象模型的其余部分。
MessageBodyReader / Writer適用於Streams,這可能就是為什么它沒有太大意義的原因。 它不假設您要使用XML。
您可能想要做的是為地圖創建一個JAXB XmlJavaTypeAdapter,然后讓JAXB進行XML創建。 您可以在JavaDoc頁面上找到更多信息:
我發現在這一個很好的職位在地鐵郵件列表在這里 。 此代碼應為您提供所需的內容。 上下文是關於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;
}
}
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(請參閱我的原始帖子以獲取示例)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.