JAX-RS: How to automatically serialize a collection when returning a Response object?

倾然丶 夕夏残阳落幕 提交于 2019-11-27 19:19:13

问题


I have a JAXB-annotated employee class:

@XmlRootElement(name = "employee")
public class Employee {

    private Integer id;
    private String name;

    ...

    @XmlElement(name = "id")
    public int getId() {
        return this.id;
    }

    ... // setters and getters for name, equals, hashCode, toString
}

And a JAX-RS resource object (I'm using Jersey 1.12)

@GET
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/")
public List<Employee> findEmployees(
    @QueryParam("name") String name, 
    @QueryParam("page") String pageNumber,
    @QueryParam("pageSize") String pageSize) {
    ...
    List<Employee> employees = employeeService.findEmployees(...);

    return employees;
}

This endpoint works fine. I get

<employees>
  <employee>
    <id>2</id>
    <name>Ana</name>
  </employee>
</employees>

However, if I change the method to return a Response object, and put the employee list in the response body, like this:

@GET
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Path("/")
public Response findEmployees(
    @QueryParam("name") String name, 
    @QueryParam("page") String pageNumber,
    @QueryParam("pageSize") String pageSize) {
    ...
    List<Employee> employees = employeeService.findEmployees(...);

    return Response.ok().entity(employees).build();
}

the endpoint results in an HTTP 500 due to the following exception:

javax.ws.rs.WebApplicationException: com.sun.jersey.api.MessageException: A message body writer for Java class java.util.ArrayList, and Java type class java.util.ArrayList, and MIME media type application/xml was not found

In the first case, JAX-RS has obviously arranged for the proper message writer to kick in when returning a collection. It seems somewhat inconsistent that this doesn't happen when the collection is placed in the entity body. What approach can I take to get the automatic JAXB serialization of the list to happen when returning a response?

I know that I can

  • Just return the list from the resource method
  • Create a separate EmployeeList class

but was wondering whether there is a nice way to use the Response object and get the list to serialize without creating my own wrapper class.


回答1:


You can wrap the List<Employee> in an instance of GenericEntity to preserve the type information:

  • http://docs.oracle.com/javaee/6/api/javax/ws/rs/core/GenericEntity.html



回答2:


You can use GenericEntity to send the collection in the Response. You must have included appropriate marshal/unmarshal library like moxy or jaxrs-jackson.

Below is the code :

    @GET
    @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
    @Path("/")
    public Response findEmployees(
        @QueryParam("name") String name, 
        @QueryParam("page") String pageNumber,
        @QueryParam("pageSize") String pageSize) {
        ...
        List<Employee> employees = employeeService.findEmployees(...);

        GenericEntity<List<Employee>> entity = new GenericEntity<List<Employee>>(Lists.newArrayList(employees))

        return Response.ok().entity(entity).build();
    }



回答3:


I resolved this issue by extending the default JacksonJsonProvider class, in particular method writeTo.

Analyzing the source code of this class I found the block where the actual type is instantiated by reflection, so I've modified the source code as below:

public void writeTo(Object value, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String,Object> httpHeaders, OutputStream entityStream) throws IOException {
    /* 27-Feb-2009, tatu: Where can we find desired encoding? Within
     *   HTTP headers?
     */
    ObjectMapper mapper = locateMapper(type, mediaType);
    JsonEncoding enc = findEncoding(mediaType, httpHeaders);
    JsonGenerator jg = mapper.getJsonFactory().createJsonGenerator(entityStream, enc);
    jg.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);

    // Want indentation?
    if (mapper.getSerializationConfig().isEnabled(SerializationConfig.Feature.INDENT_OUTPUT)) {
        jg.useDefaultPrettyPrinter();
    }
    // 04-Mar-2010, tatu: How about type we were given? (if any)
    JavaType rootType = null;

    if (genericType != null && value != null) {
        /* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root
         *    type since it prevents polymorphic type serialization. Since we really
         *    just need this for generics, let's only use generic type if it's truly
         *    generic.
         */
        if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type'
            /* This is still not exactly right; should root type be further
             * specialized with 'value.getClass()'? Let's see how well this works before
             * trying to come up with more complete solution.
             */

            //**where the magic happens**
            //if the type to instantiate implements collection interface (List, Set and so on...)
            //Java applies Type erasure from Generic: e.g. List<BaseRealEstate> is seen as List<?> and so List<Object>, so Jackson cannot determine @JsonTypeInfo correctly
            //so, in this case we must determine at runtime the right object type to set
            if(Collection.class.isAssignableFrom(type))
            {
                Collection<?> converted = (Collection<?>) type.cast(value);
                Class<?> elementClass = Object.class;
                if(converted.size() > 0)
                    elementClass = converted.iterator().next().getClass();
                //Tell the mapper to create a collection of type passed as parameter (List, Set and so on..), containing objects determined at runtime with the previous instruction
                rootType = mapper.getTypeFactory().constructCollectionType((Class<? extends Collection<?>>)type, elementClass);
            }
            else
                rootType = mapper.getTypeFactory().constructType(genericType);
            /* 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where
             *    type degenerates back into "Object.class" (as is the case with plain TypeVariable,
             *    for example), and not use that.
             */
            if (rootType.getRawClass() == Object.class) {
                rootType = null;
            }
        }
    }
    // [JACKSON-578]: Allow use of @JsonView in resource methods.
    Class<?> viewToUse = null;
    if (annotations != null && annotations.length > 0) {
        viewToUse = _findView(mapper, annotations);
    }
    if (viewToUse != null) {
        // TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer)
        ObjectWriter viewWriter = mapper.viewWriter(viewToUse);
        // [JACKSON-245] Allow automatic JSONP wrapping
        if (_jsonpFunctionName != null) {
            viewWriter.writeValue(jg, new JSONPObject(this._jsonpFunctionName, value, rootType));
        } else if (rootType != null) {
            // TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer)
            mapper.typedWriter(rootType).withView(viewToUse).writeValue(jg, value);
        } else {
            viewWriter.writeValue(jg, value);
        }
    } else {
        // [JACKSON-245] Allow automatic JSONP wrapping
        if (_jsonpFunctionName != null) {
            mapper.writeValue(jg, new JSONPObject(this._jsonpFunctionName, value, rootType));
        } else if (rootType != null) {
            // TODO: change to use 'writerWithType' for 2.0 (1.9 could use, but let's defer)
            mapper.typedWriter(rootType).writeValue(jg, value);
        } else {
            mapper.writeValue(jg, value);
        }
    }
}


来源:https://stackoverflow.com/questions/11771830/jax-rs-how-to-automatically-serialize-a-collection-when-returning-a-response-ob

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!