【问题标题】:How to return objects in Jersey 2.x如何在 Jersey 2.x 中返回对象
【发布时间】:2016-02-29 14:53:42
【问题描述】:

我有一个管理 Parada 对象的网络服务。我想要实现的目标似乎很简单:返回这些对象的列表:

List<Parada> list

这个列表是使用一个使用另一个 DAO 类的 Service 类返回的,只是将其注释掉。

此外,我的常见做法是每个 Web 方法都使用 ResponseBuilder 返回一个响应,如下所示:

return Response.ok(obj, MediaType.APPLICATION_JSON).build();

这是我的一种网络方法的示例:

@GET
@Consumes(value = MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
@Path("{idParadaGtfs}")
public Response getParadasPorIdGtfs(
    @PathParam(value = "idParadaGtfs") Integer pCodigoParadaEnGtfs
){
    try{
        getServiceIfNecessary();
        List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
        return Response.ok(paradas, MediaType.APPLICATION_JSON).build();
    }catch(HibernateException e){
        String msg = "Error HibernateException: " + e.getMessage();
        LogHelper.logError(logger, msg, true);
        e.printStackTrace();
        return Response.serverError().tag(msg).build();
    }catch(Exception e){
        String msg = "Error Exception: " + e.getMessage();
        LogHelper.logError(logger, msg, true);
        e.printStackTrace();
        return Response.serverError().tag(msg).build();
    }

}

很遗憾,我没有收到任何对象,并且每次执行上述 Web 方法时都会收到以下错误:

nov 26, 2015 2:20:16 PM org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo
GRAVE: MessageBodyWriter not found for media type=application/json, type=class java.util.ArrayList, genericType=java.util.List<model.Parada>.

我必须实现什么才能让我的网络方法使用列表构建响应?

谢谢!

编辑

我已经能够通过进行一些更改和添加来使其工作,我现在将对其进行描述。

首先,我添加了一个 Parada 容器类,ParadaContainer:

    import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.annotation.XmlRootElement;

import com.ingartek.ws.paradasasociadasws.model.Parada;

@XmlRootElement
public class ParadaContainer implements Serializable {

    private static final long serialVersionUID = 6535386309072039406L;
    private List<Parada> paradas;

    public ParadaContainer(ArrayList<Parada> pParadas) {
        this.setParadas(pParadas);
    }

    public List<Parada> getParadas() {
        return paradas;
    }

    public void setParadas(List<Parada> paradas) {
        this.paradas = paradas;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("ParadaContainer [");
        if (paradas != null) {
            builder.append("paradas=");
            for(Parada p : paradas){
                builder.append(p.toString());
            }

        }
        builder.append("]");
        return builder.toString();
    }

}

现在,我不返回 Parada 对象列表,而是返回单个 ParadaContainer 对象:

ParadaContainer paradas = new ParadaContainer(new ArrayList<Parada>(service.getParadas()));

return Response
        .ok(paradas)
        .type(MediaType.APPLICATION_JSON)
        .build();

我不知道它们是否是强制性的,但我已经创建了另一个类 (MyObjectMapperProvider)...

import javax.ws.rs.ext.ContextResolver;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {

    final ObjectMapper defaultObjectMapper;

    public MyObjectMapperProvider() {
        defaultObjectMapper = createDefaultMapper();
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
            return defaultObjectMapper;
    }

    private static ObjectMapper createDefaultMapper() {
        final ObjectMapper result = new ObjectMapper();
        result.configure(SerializationFeature.INDENT_OUTPUT, true);

        return result;
    }
}

...并编辑了我的 Application 类并添加了一些行(请参阅 *Jackson * 评论,直到 Classes de Servicios 评论):

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.ws.rs.core.Application;

import org.glassfish.jersey.jackson.JacksonFeature;

import com.ingartek.ws.paradasasociadasws.ws.ParadasWS;

public class App extends Application {

    private final Set<Class<?>> classes;

    public App() {
        HashSet<Class<?>> c = new HashSet<Class<?>>();
        // Filtro CORS:
        c.add(CORSFilter.class);

        // Jackson
        c.add(MyObjectMapperProvider.class);
        c.add(JacksonFeature.class);

        // Clases de Servicios:
        c.add(ParadasWS.class);
        classes = Collections.unmodifiableSet(c);
    }

    @Override
    public Set<Class<?>> getClasses() {
        return classes;
    }

}

之后,我通过向它们添加一些注释来编辑我的类模型(@XmlRootElement 和 @JsonProperty;删除了不相关的 getter、setter、hashCode、equals 和 toString 方法):

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;

import com.fasterxml.jackson.annotation.JsonProperty;

@XmlRootElement(name = "grupo")
@Entity
@Table(name = "grupos_cercania_exacta")
public class Grupo implements Serializable {

    @Transient
    private static final long serialVersionUID = -5679016396196675191L;

    @JsonProperty("id")
    @Id
    @Column(name = "id_grupo")
    private Integer id;

    ...

}

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;

import com.fasterxml.jackson.annotation.JsonProperty;

@XmlRootElement(name = "operador")
@Entity
@Table(name = "operadores_asociados")
public class Operador implements Serializable {

    @Transient
    private static final long serialVersionUID = -7557099187432476588L;

    /*
        Atributos
     */
    @JsonProperty("codigo")
    @Id
    @Column(name = "codigo_operador", insertable = false, updatable = false)
    private Integer codigo;
    @JsonProperty("nombre")
    @Column(name = "descripcion_corta", insertable = false, updatable = false)
    private String nombre;
    @JsonProperty("descripcion")
    @Column(name = "descripcion_larga", insertable = false, updatable = false)
    private String descripcion;
    @JsonProperty("web")
    @Column(name = "direccion_web", insertable = false, updatable = false)
    private String web;
    @JsonProperty("telefono")
    @Column(name = "telefono", insertable = false, updatable = false)
    private String telefono;

    ...

}

import java.io.Serializable;
import java.util.UUID;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;

import com.fasterxml.jackson.annotation.JsonProperty;

@XmlRootElement(name = "parada")
@Entity
@Table(name = "paradas_asociadas")
public class Parada implements Serializable {

    @Transient
    private static final long serialVersionUID = -3594254497389126197L;

    @JsonProperty("id")
    @Id
    @Column(name = "id")
    private UUID id;
    @JsonProperty("codigoMunicipio")
    @Column(name = "codigo_municipio")
    private Integer codigoMunicipio;
    @JsonProperty("nombre")
    @Column(name = "nombre")
    private String nombre;
    @JsonProperty("descripcion")
    @Column(name = "descripcion")
    private String descripcion;
    @JsonProperty("idGtfs")
    @Column(name = "id_gtfs")
    private Integer idGtfs;
    @JsonProperty("idWs")
    @Column(name = "id_ws")
    private Integer idWs;
    @JsonProperty("latitud")
    @Column(name = "latitud")
    private Double latitud;
    @JsonProperty("longitud")
    @Column(name = "longitud")
    private Double longitud;
    @JsonProperty("utmX")
    @Column(name = "utm_x")
    private Double utmX;
    @JsonProperty("utmY")
    @Column(name = "utm_y")
    private Double utmY;
    @JsonProperty("grupo")
    @ManyToOne
    @JoinColumn(name = "grupo_cercania_exacta_id")
    private Grupo grupo;
    @JsonProperty("operador")
    @ManyToOne
    @JoinColumn(name = "operador")
    private Operador operador;

    ...

}

我不得不承认,在进行这些更改之后,我遇到了一些问题。敏锐的人可能已经意识到之前的 Parada 类缺少一个属性:缺少 Point 属性。这个属性给我带来了一些问题,即没有序列化器和序列化器阻止了我创建成功的 JSON。于是我google了一下,发现了三个选项:

  1. 删除点项目。这是我的最终选择,因为 Point 是多余的,因为存在纬度和经度元素,而且它只会打扰或混淆最终用户。
  2. 创建自定义序列化器和反序列化器。幸运的是我找到了以下link,它描述了创建它们的过程。以下在here 中有描述:

将这些注释添加到我们的坐标字段中:

@JsonSerialize(using = PointToJsonSerializer.class)
@JsonDeserialize(using = JsonToPointDeserializer.class)

创建这样的序列化程序:

import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.vividsolutions.jts.geom.Point;

public class PointToJsonSerializer extends JsonSerializer<Point> {

    @Override
    public void serialize(Point value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {

        String jsonValue = "null";
        try
        {
            if(value != null) {             
                double lat = value.getY();
                double lon = value.getX();
                jsonValue = String.format("POINT (%s %s)", lat, lon);
            }
        }
        catch(Exception e) {}

        jgen.writeString(jsonValue);
    }

}

创建这样的反序列化器:

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.PrecisionModel;

public class JsonToPointDeserializer extends JsonDeserializer<Point> {

    private final static GeometryFactory geometryFactory = new GeometryFactory(new PrecisionModel(), 26910); 

    @Override
    public Point deserialize(JsonParser jp, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {

        try {
            String text = jp.getText();
            if(text == null || text.length() <= 0)
                return null;

            String[] coordinates = text.replaceFirst("POINT ?\\(", "").replaceFirst("\\)", "").split(" ");
            double lat = Double.parseDouble(coordinates[0]);
            double lon = Double.parseDouble(coordinates[1]);

            Point point = geometryFactory.createPoint(new Coordinate(lat, lon));
            return point;
        }
        catch(Exception e){
            return null;
        }
    }

}
  1. 最后一个选项是使用 Jackson Datatype JTS 库,其 github 存储库位于 here

我花了几个小时才找到这些解决方案,但最终我得到了它们。希望它对某人有所帮助。谢谢!

【问题讨论】:

    标签: java jax-rs jersey-2.0


    【解决方案1】:

    不允许发回列表。可能是因为 List 没有 @XmlRootElement 符号。您可以创建自己的容器:

    @XmlRootElement
    public class ParadaContainer implements Serializable {
        private List<Parada> list;
    
        public List<Parada> getList() {
            return list;
        }
    
        public void setList(List<Parada> list) {
            this.list = list;
        }
    }
    

    你的部分看起来像:

    try{
            getServiceIfNecessary();
            List<Parada> paradas = service.getParadas(pCodigoParadaEnGtfs);
            ParadaContainer paradaContainer = new ParadaContainer();
            paradaContainer.setList(paradas);
            return Response.ok(paradaContainer, MediaType.APPLICATION_JSON).build();
        }
    

    【讨论】:

    • 感谢您的帮助,但它不起作用...这是我得到的错误:2015 年 11 月 26 日下午 4:05:18 org.glassfish.jersey.message.internal .WriterInterceptorExecutor$TerminalWriterInterceptor aroundWriteTo GRAVE:找不到媒体类型 = 应用程序/json,类型 = com.ingartek.ws.paradasasociadasws.model.extend.ParadaContainer 类的 MessageBodyWriter,genericType = com.ingartek.ws.paradasasociadasws.model.extend 类。 ParadaContainer。
    【解决方案2】:

    要么你没有 JSON 提供程序(我猜你有),要么你正在使用 MOXy。在后一种假设下,对于 MOXy,它需要知道类型信息才能进行序列化。当您返回 Response 时,您正在包装对象,这会带走类型信息(因为类型擦除),而不是如果您正在这样做

    @GET
    public List<Parada> get() {}
    

    这里的类型信息是已知的。但是在做

    @GET
    public Response get() {
        List<Parada> list..
         return Response.ok(list)...
    }
    

    在实体到达处理的序列化阶段时,该类型被隐藏和擦除。

    为了解决这个问题,引入了GenericEntity

    通常类型擦除会删除通用类型信息,这样一个包含例如List&lt;String&gt; 类型实体的响应实例在运行时似乎包含一个原始List&lt;?&gt;。当需要泛型类型选择合适的MessageBodyWriter时,可以使用该类来包装实体并捕获其泛型类型。

    所以你可以这样做

    List<Parada> paradas = ...
    GenericEntity<List<Parada>> entity = new GenericEntity<List<Parada>>(paradas){};
    return Response.ok(entity, ...)...
    

    第二个选项,不是使用 MOXy,而是使用 Jackson。使用 Jackson,不需要类型信息(在大多数情况下),因为序列化程序只是内省和 bean bean 属性来获取数据。

    【讨论】:

    • 如果你想使用 Jackson 作为你的 JSON 提供者而不是 MOXy(假设你正在使用它)
    • 根据您在另一个答案中的评论,您似乎甚至没有 JSON 提供程序。在发布此问题之前,您是否能够让 任何 JSON 工作?
    • 不,我不是,但我已经能够使用 Jackson Parser。我正在编辑我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 2018-04-07
    • 2017-08-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多