【问题标题】:JAX-RS: How to automatically serialize a collection when returning a Response object?JAX-RS:返回响应对象时如何自动序列化集合?
【发布时间】:2012-07-31 02:12:21
【问题描述】:

我有一个带有 JAXB 注释的员工类:

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

还有一个 JAX-RS 资源对象(我使用的是 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;
}

此端点工作正常。我明白了

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

但是,如果我将方法更改为返回 Response 对象,并将员工列表放入响应正文中,如下所示:

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

由于以下异常,端点导致 HTTP 500:

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

在第一种情况下,JAX-RS 显然已经安排适当的消息编写器在返回集合时启动。将集合放在实体主体中时不会发生这种情况似乎有些不一致。在返回响应时,我可以采取什么方法来实现列表的自动 JAXB 序列化?

我知道我可以

  • 只需从资源方法中返回列表
  • 创建一个单独的EmployeeList

但想知道是否有一个很好的方法来使用Response 对象并让列表序列化创建我自己的包装类。

【问题讨论】:

    标签: jaxb jersey jax-rs


    【解决方案1】:

    您可以将List&lt;Employee&gt; 包装在GenericEntity 的实例中以保留类型信息:

    【讨论】:

    【解决方案2】:

    我通过扩展默认的 JacksonJsonProvider 类解决了这个问题,特别是方法 writeTo。

    分析这个类的源码发现了反射实例化实际类型的块,所以修改源码如下:

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

    【讨论】:

      【解决方案3】:

      您可以使用 GenericEntity 在响应中发送集合。您必须包含适当的 marshal/unmarshal 库,例如 moxy 或 jaxrs-jackson。

      下面是代码:

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

      【讨论】:

        猜你喜欢
        • 2012-02-06
        • 2020-12-07
        • 1970-01-01
        • 1970-01-01
        • 2018-02-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-09
        相关资源
        最近更新 更多