JAXRS / RestEasy中的@Produces集合

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

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

1

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

一切都在#1,#2,#3中起作用,但第四个例子抛出:

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

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

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

1和2的工作原理是因为Jackson被用于JSON,它通常不需要知道类型信息,因为它只是内省属性。

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

4不起作用,因为没有类型信息。 它被类型擦除所抹去 。 这就是GenericEntity拯救的地方。 它存储类型信息。

GenericEntity

通常,类型擦除会删除generics类型信息,以便包含例如List类型的实体的Response实例在运行时似乎包含原始List 。 当需要generics类型来选择合适的MessageBodyWriter ,可以使用此类来包装实体并捕获其generics类型。

swch,我为集合创建了一个MessageBodyWriter示例(Set,List等)。另外,有些人可以分析xml根名称,gzip和缓存的注释……玩得开心!

 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 { 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 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 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("".getBytes()); } else { Set classes = new HashSet(); 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("".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("".getBytes()); } } catch (JAXBException e) { throw new IOException(e); } } }