【问题标题】:@Produces collection in JAXRS / RestEasy@Produces 在 JAXRS / RestEasy 中的集合
【发布时间】:2016-01-27 13:55:07
【问题描述】:

我发现了一些我无法理解的奇怪行为。

我已经测试了 4 个类似的例子:

1

@GET
@Produces(MediaType.APPLICATION_JSON)
public Response produce() {
    List<Book> books = Arrays
            .asList(new Book[] { 
                    new Book("aaa", "AAA", "12345"), 
                    new Book("bbb", "BBB", "09876") 
                    });
    return Response.ok(books).build();
}

2

@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Book> produce() {
    List<Book> books = Arrays
            .asList(new Book[] { 
                    new Book("aaa", "AAA", "12345"), 
                    new Book("bbb", "BBB", "09876") 
                    });
    return books;
}

3

@GET
@Produces(MediaType.APPLICATION_XML)
public List<Book> produce() {
    List<Book> books = Arrays
            .asList(new Book[] { 
                    new Book("aaa", "AAA", "12345"), 
                    new Book("bbb", "BBB", "09876") 
                    });
    return books;
}

4

@GET
@Produces(MediaType.APPLICATION_XML)
public Response produce() {
    List<Book> books = Arrays
            .asList(new Book[] { 
                    new Book("aaa", "AAA", "12345"), 
                    new Book("bbb", "BBB", "09876") 
                    });
    return Response.ok(books).build();
}

#1、#2、#3 一切正常,但第 4 个示例抛出:

找不到响应对象类型的 MessageBodyWriter: 媒体类型的java.util.Arrays$ArrayList:application/xml。

我在 Wildfly 9 上运行它,我想知道它是否与 RestEasy 或 JaxRS 一般有关?我知道我可以通过在 GenericEntity 中包装集合来修复它,但我不理解这种不一致的行为。

【问题讨论】:

  • 这可能取决于构建最终字符串文档(JSON 或 XML)所需的内容。对于 XML,了解 Generic 属性的 Class 可能很重要,因为它用于构建标记。这可能是 JSON 继续而不是 XML 的原因。通过阅读this article,问题显然是在使用响应答案的处理过程中类型丢失了。

标签: java jax-rs resteasy wildfly-9


【解决方案1】:

问题是缺少类型信息。这是处理 XML 序列化的 JAXB 所必需的。

1 和 2 有效,因为 Jackson 用于 JSON,它通常不需要知道类型信息,因为它只是内省属性。

3 有效,因为类型信息通过方法返回类型已知。

4 不起作用,因为没有类型信息。它被type erasure 删除。这就是GenericEntity 来救援的地方。它存储类型信息。

GenericEntity

通常,类型擦除会删除泛型类型信息,以使包含 List&lt;String&gt; 类型实体的 Response 实例在运行时似乎包含原始 List&lt;?&gt;。当需要泛型类型来选择合适的MessageBodyWriter时,可以使用该类来包装实体并捕获其泛型类型。

【讨论】:

  • 据我了解,类型擦除会删除所有泛型类型,因此 List 在 #3 和 #4 两种情况下都转换为简单的 List。为什么 JAXRS(或实际上是 JAXB)知道在 #3 中使用哪个 MessageBodyWriter 并且在 #4 中存在问题? Response.ok() 或 ResponseBuilder.entity() 不只是 #3 案例的附加包装器吗?
  • 方法签名具有#3中的类型,如我的回答中所述。
【解决方案2】:

swch,我为集合(集合、列表等)创建了一个 MessageBodyWriter 示例 另外,有些人可以分析 xml 根名称、gzip 和缓存的 Annotation...玩得开心!

import java.io.IOException;
import java.io.OutputStream;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;

import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.namespace.QName;

@Provider
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public class CollectionProvider
  implements MessageBodyWriter<Collection>
{
  static final byte[] COMMA = ",".getBytes();
  static final byte[] ARRAY_START = "[".getBytes();
  static final byte[] ARRAY_END = "]".getBytes();
  static final byte[] NULL = "null".getBytes();
  static final QName OBJECT = new QName(null, "object");

  @Override
  public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType)
  {
    if (!Collection.class.isAssignableFrom(type))
      return false;

    if (genericType == null || !(genericType instanceof ParameterizedType))
      return false;

    Type[] args = ((ParameterizedType) genericType).getActualTypeArguments();

    for (Type arg: args)
    {
      if (arg instanceof TypeVariable) // can't mashal Collection<T>
        return false;

      if (!(arg instanceof Class))
        return false;
    }

    String type = mediaType.getType().toLowerCase();
    String subtype = mediaType.getSubtype().toLowerCase();

    return type.equals("application") && 
      (subtype.startsWith("json") || subtype.startsWith("xml"));
  }

  @Override
  public long getSize(Collection list, Class<?> c, Type type, Annotation[] annotation, MediaType mediaType)
  {
    return -1;
  }

  @Override
  public void writeTo(Collection list, Class<?> c, Type type, Annotation[] annotation, MediaType mediaType,
                      MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream)
    throws IOException, WebApplicationException
  {
    try
    {
      boolean json = mediaType.getSubtype().toLowerCase().startsWith("json");

      if (list.isEmpty())
      {
        if(json)
        {
          outputStream.write(ARRAY_START);
          outputStream.write(ARRAY_END);
        }
        else
          outputStream.write("<list/>".getBytes());
      }
      else
      {
        Set<Class> classes = new HashSet<Class>();

        for (Type clazz: ((ParameterizedType) type).getActualTypeArguments())
          classes.add((Class) clazz);

        JAXBContext jc = JAXBContext.newInstance(classes.toArray(new Class[classes.size()]));
        Marshaller m = jc.createMarshaller();
        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);

        if(json)
        {
          m.setProperty("eclipselink.media-type", MediaType.APPLICATION_JSON);
          m.setProperty("eclipselink.json.include-root", false);
        }

        if(json)
          outputStream.write(ARRAY_START);
        else
          outputStream.write("<list>".getBytes());

        for (Iterator it = list.iterator(); it.hasNext();)
        {
          Object object = it.next();

          if(json)
          {
            if (object == null) // Allow nullabale value collections
              outputStream.write(NULL);
            else
              m.marshal(new JAXBElement(OBJECT, object.getClass(), object), outputStream);

            if (it.hasNext())
              outputStream.write(COMMA);
          }
          else if (object != null) // null in xml? xm...
            m.marshal(object, outputStream); // <-- requered XmlRoot annotation
        }

        if(json)
          outputStream.write(ARRAY_END);
        else
          outputStream.write("</list>".getBytes());
      }
    }
    catch (JAXBException e)
    {
      throw new IOException(e);
    }
  }
}

【讨论】:

    猜你喜欢
    • 2013-09-17
    • 2014-10-28
    • 2019-03-19
    • 2020-03-08
    • 1970-01-01
    • 2022-06-14
    • 2019-10-02
    • 1970-01-01
    • 2011-02-21
    相关资源
    最近更新 更多