17

我有一个带有 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对象并让列表序列化而不创建我自己的包装类。

4

3 回答 3

26

您可以将 包装List<Employee>在一个实例中GenericEntity以保留类型信息:

于 2012-08-02T10:05:27.917 回答
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();
    }
于 2016-02-17T18:56:15.810 回答
-1

我通过扩展默认的 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);
        }
    }
}
于 2015-01-30T08:49:36.743 回答