JAXRS / RestEasy 中的@Produces 集合
@Produces collection in JAXRS / RestEasy
我发现了一些我无法理解的奇怪行为。
我测试了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 个示例抛出:
Could not find MessageBodyWriter for response object of type:
java.util.Arrays$ArrayList of media type: application/xml.
我在 Wildfly 9 上 运行 它,我想知道它是否与 RestEasy 或 JaxRS 一般相关?我知道我可以通过在 GenericEntity 中包装集合来修复它,但我不明白这种不一致的行为。
问题是缺少类型信息。这是处理 XML 序列化的 JAXB 所必需的。
1 和 2 之所以有效,是因为 Jackson 被用于 JSON,并且它通常不需要知道类型信息,因为它只是内省属性。
3 之所以有效,是因为类型信息是通过方法 return type.
获知的
4 不起作用,因为没有类型信息。它被 type erasure 删除了。这就是 GenericEntity
派上用场的地方。它存储类型信息。
Normally type erasure removes generic type information such that a Response
instance that contains, e.g., an entity of type List<String>
appears to contain a raw List<?>
at runtime. When the generic type is required to select a suitable MessageBodyWriter
, this class may be used to wrap the entity and capture its generic type.
swch,我为集合(集合、列表等)创建了一个 MessageBodyWriter 示例
此外,有些人可以分析 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<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);
}
}
}
我发现了一些我无法理解的奇怪行为。
我测试了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 个示例抛出:
Could not find MessageBodyWriter for response object of type: java.util.Arrays$ArrayList of media type: application/xml.
我在 Wildfly 9 上 运行 它,我想知道它是否与 RestEasy 或 JaxRS 一般相关?我知道我可以通过在 GenericEntity 中包装集合来修复它,但我不明白这种不一致的行为。
问题是缺少类型信息。这是处理 XML 序列化的 JAXB 所必需的。
1 和 2 之所以有效,是因为 Jackson 被用于 JSON,并且它通常不需要知道类型信息,因为它只是内省属性。
3 之所以有效,是因为类型信息是通过方法 return type.
获知的4 不起作用,因为没有类型信息。它被 type erasure 删除了。这就是 GenericEntity
派上用场的地方。它存储类型信息。
Normally type erasure removes generic type information such that a
Response
instance that contains, e.g., an entity of typeList<String>
appears to contain a rawList<?>
at runtime. When the generic type is required to select a suitableMessageBodyWriter
, this class may be used to wrap the entity and capture its generic type.
swch,我为集合(集合、列表等)创建了一个 MessageBodyWriter 示例 此外,有些人可以分析 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<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);
}
}
}