如何在Jersey 2.x中返回对象

时间:2015-11-26 13:22:06

标签: java jax-rs jersey-2.0

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

List<Parada> list

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

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

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 *评论直到 Clases 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属性。这个属性给我带来了一些问题,也就是说,没有Seri​​alizer和Serializer阻止我创建一个成功的JSON。所以我用Google搜索出来并找到了三个选项:

  1. 删除Point项。这是我的最终选择,由于Latitude和Longitude元素的存在,Point是多余的,因为它只会让最终用户感到困扰或混淆。
  2. 创建自定义序列化程序和反序列化程序。幸运的是,我找到了以下link,它描述了创建它们的过程。 here
  3. 中描述了以下内容
      

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

    @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数据类型JTS 库,其github存储库位于here
    2. 我持续了几个小时才能找到这些解决方案,但最终我得到了它们。希望对某人有帮助。谢谢!

2 个答案:

答案 0 :(得分:3)

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

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

此处类型信息是已知的。但是做了

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

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

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

  

通常,类型擦除会删除泛型类型信息,以便包含例如类型List<String>的实体的Response实例在运行时似乎包含原始List<?>。当需要泛型类型来选择合适的MessageBodyWriter时,此类可用于包装实体并捕获其泛型类型。

所以你可以做到

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

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

答案 1 :(得分:0)

不允许发回List。可能是因为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();
    }