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

Posted

技术标签:

【中文标题】如何在 Jersey 2.x 中返回对象【英文标题】:How to return objects in 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了一下,发现了三个选项:

    删除点项目。这是我的最终选择,因为 Point 是多余的,因为存在纬度和经度元素,而且它只会打扰或混淆最终用户。 创建自定义序列化器和反序列化器。幸运的是我找到了以下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;
        
    


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

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

【问题讨论】:

【参考方案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 属性来获取数据。

【讨论】:

这个链接值得阅读吗? jersey.java.net/documentation/latest/media.html#json.jackson 如果你想使用 Jackson 作为你的 JSON 提供者而不是 MOXy(假设你正在使用它) 根据您在另一个答案中的评论,您似乎甚至没有 JSON 提供程序。在发布此问题之前,您是否能够让 任何 JSON 工作? 不,我不是,但我已经能够使用 Jackson Parser。我正在编辑我的答案。

以上是关于如何在 Jersey 2.x 中返回对象的主要内容,如果未能解决你的问题,请参考以下文章

如何在Java Jersey REST服务中强制使用queryparams?

如何使用 Jersey JSON POJO 支持?

如何在 Spring MVC 中使用 Jackson 和 Jersey 2 Client 反序列化 Joda DateTime?

如何在球衣中使用 swagger 和 ResourceConfig?

如何将PNG图像从Jersey REST服务方法返回到浏览器

Jersey 2.x 从Maven Archetype 创建一个新项目