2

我正在使用 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还包含一个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 的“带有 JAX-RS 的 RESTful Java”一书。MessageBodyWriter它在帮助设计 JAX-RS 服务方面非常有用,但我在相关部分中找不到有关该功能的足够详细信息。我的互联网搜索也没有产生任何可以引导我朝着正确方向前进的东西。

如果有人能帮助我弄清楚如何正确实现MessageBodyWriter(and 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

我认为原因是缺少“正确的设置器”,因此解组器不会尝试将项目添加到 aCollection但我不知道该设置器的外观。如果有人知道如何做到这一点,我将不胜感激。

提前致谢。

编辑2:

顺便说一句,我已经看到 Chris 的回复,他建议使用@XmlJavaTypeAdapter. 我已经尝试过这个建议,它让我接近我需要的 XML。但是,我使用 @XmlJavaTypeAdapter 获得的 XML 在其中有一个额外的级别(<class><items><item>而不是<items><item>--- 如我的示例中所见,我将ConcurrentHashMap实例作为类的成员变量)。我似乎也无法更改各个地图项的元素名称(它们总是被称为“项目”)。

这些都不是什么大问题,我可以做出必要的改变,并在必要时接受它们。但是,如果可能的话,我希望一开始就没有它们。出于教育目的,我还想了解为什么 EDIT 1 中的代码不能用于解组(以及如何修复它,如果可能的话)。

提前感谢所有帮助。

4

2 回答 2

1

我认为您的问题是您试图将 JAXB 与 MessageBodyReader/MessageBodyWriter 混合使用。您有一个 JAXB 对象,因此您真的不希望 RestEasy 使用您的 MessageBodyWriter 完成所有序列化,因为它不会考虑您的 JAXB 对象。您可以这样做,但您也需要序列化对象模型的其余部分。

MessageBodyReader/Writer 适合与 Streams 一起使用,这可能是它没有多大意义的原因。它不假设您将使用 XML。

您可能想要做的是为地图创建一个 JAXB XmlJavaTypeAdapter 并让 JAXB 创建 XML。您可以在 JavaDoc 页面上找到更多信息:

http://download.oracle.com/javase/6/docs/api/javax/xml/bind/annotation/adapters/XmlJavaTypeAdapter.html

我确实在此处的 Metro 邮件列表中找到了一篇关于此的好帖子。这段代码应该给你你想要的。上下文围绕 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; 
  } 
} 
于 2011-08-08T13:16:58.903 回答
0

XmlJavaTypeAdapter方法可以正常工作,因为它允许 JAXB 执行序列化并返回。但是,它没有给我所需的 XML。我认为这无关紧要,但最后结果证明我需要以那种形式获取 XML。

我使用的 XML@XmlJavaTypeAdapter在其中有一个额外的级别(类似于<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(请参阅我的原始帖子以获取示例)。

于 2011-08-27T09:45:07.970 回答