如何使用 RestEasy 和 Tomcat 让 MessageBodyWriter 与 HashMap 一起使用?

发布于 2024-11-28 08:23:48 字数 6045 浏览 1 评论 0原文

我正在使用 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

我认为原因是缺少“适当的设置器”,因此解组器不会尝试将项目添加到 Collection 中,但我不知道该设置器会是什么样子。如果有人知道如何做到这一点,我将不胜感激。

提前致谢。

编辑2:

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

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

预先感谢您的所有帮助。

I am developing a JAX-RS webservice using RestEasy 2.2.2 to be deployed to Tomcat 7. The service returns (should return) XML through the use of JAXB. Returned XML should contain representations of ConcurrentHashMap similar to how it is used in the following code:

@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
    }
}

The Item class also contains a ConcurrentHashMap that needs to be serialized to XML.

This is the resource class:

@Path("/items")
public class ItemResource 
{
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public ItemCollection getAllItems() 
    {
        // get itemManager
        return itemManager.getItems(); // ItemManager holds an instance of ItemCollection
    }
}

This code runs, but produces an XML with no content:

<items>
    <item/>
</items>

What I am trying to get as an output is something like:

<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>

I found out that a MessageBodyWriter implementation is required where builtin functionality is not sufficient. I tried to come up with a MessageBodyWriter implementation to marshal the ConcurrentHashMap, but I have not been able to get it working so far (i.e. I can get the code being called, but it stops with various exceptions).

It seems I do not quite grasp how MessageBodyWriter (and MessageBodyReader) interfaces should be implemented and used. I have Bill Burke's "RESTful Java with JAX-RS" book. It is very useful in terms of helping design JAX-RS services but I could not find enough details about the MessageBodyWriter functionality in the related section. My internet searches did not yield anything that could guide me in the right direction, either.

I would appreciate if anyone can help me in figuring out how to implement the MessageBodyWriter (and MessageBodyReader) interfaces properly. I do not know if I am missing an annotation, misplacing one or whether I need a completely new approach.

Thanks in advance for helping.

EDIT:

Modifying the code to following gets me halfway there:

@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();
    }
}

This generates the XML that I need (sample included above). However, this code does not work when it comes to unmarshalling. I get the following exception:

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

I assume the reason is the lack of a "proper setter" so that the unmarshaller does not try to add items into a Collection but I do not know how that setter would look like. If anyone knows how to do this, I would appreciate the help.

Thanks in advance.

EDIT 2:

BTW, I have seen Chris' reply where he suggests using @XmlJavaTypeAdapter. I have tried the suggestion and it gets me close to the XML I need. However, the XML I get using @XmlJavaTypeAdapter has an extra level in it (<class><items><item> instead of <items><item> --- as seen in my examples, I have the ConcurrentHashMap instances as a member variable of a class). I also can't seem to change the element name of the individual map items (they are always called "item").

These are not big problems and I can make the necessary changes and live with them if necessary. If possible, however, I would like to not have them in the first place. For educational purposes, I would also like to understand why the code in EDIT 1 does not work for unmarshalling (and how to fix it, if possible).

Thanks in advance for all the help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

同展鸳鸯锦 2024-12-05 08:23:48

我认为你的问题是你试图将 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; 
  } 
} 

I think your problem is that you are trying to mix JAXB with your MessageBodyReader/MessageBodyWriter. You have a JAXB object so you do not really want RestEasy to do all of the serialization using your MessageBodyWriter as it would not take into account your JAXB objects. You could do it this way, but you would need to serialize the rest of your object model too.

The MessageBodyReader/Writer is geared for working with Streams which is probably why it did not make much sense. It makes no assumptions that you are going to XML.

What you probably want to do is create an JAXB XmlJavaTypeAdapter for the map and let JAXB do the XML creation. You can find more information on the JavaDoc page:

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

I did find one good post on this over the Metro mailing list here. This code should give you what you want. The context was around JAX-WS but you are looking specifically for the JAXB annotation to do a custom binding.

@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; 
  } 
} 
向日葵 2024-12-05 08:23:48

XmlJavaTypeAdapter 方法工作正常,因为它允许 JAXB 进行序列化并返回。但是,它没有提供我需要的 XML。我认为这不会太重要,但最后发现我需要以这种形式获取 XML。

我使用 @XmlJavaTypeAdapter 获得的 XML 有一个额外的级别(类似于 而不是 )。我在论坛上找到了一篇帖子(如果我能再次找到它,我将添加链接),它解释了这个问题,并指出为了获取这种类型的 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(有关示例,请参阅我原来的帖子)。

The XmlJavaTypeAdapter method works OK in that it allows JAXB do the serialization and back. However, it does not give me the XML that I need. I thought that would not matter too much, but at the end it turned out that I need to get the XML in that form.

The XML I get using @XmlJavaTypeAdapter has an extra level in it (similar to <collection><mapitem><item><mapitem><item> instead of <collection><item><item>). I found a post on a forum (I will add the link if I can find it again) that explains this issue and indicates that in order to get this type of XML, JAXB needs to see a collection, rather than a map.

So, with the hope that it will be useful to others, here's what I did:

First, I defined this interface:

public interface MyCollectionInterface
{
    public String getItemId();
}

Then I modified the items to be put in the collection as follows:

public class CollectionItem implements MyCollectionInterface
{
    @XmlAttribute
    private String id; // This was already a class member

    @Override
    public String getItemId() 
    {
        return id; 
    }
}

The idea is to have a known way to get the key to be used with the HashMap.

Then came the declaration for 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
}

Now, modifying the code to the following form puts everything into place:

@XmlRootElement(name="items")
@XmlAccessorType(XmlAccessType.NONE)
public class ItemCollection 
{
    @XmlElement(name="item")
    private MyDictionary<CollectionItem> items;

    public ItemCollection()
    {
        items = new MyDictionary<CollectionItem>();
    }
}

This produces the XML I was after (see my original post for an example).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文