8

我正在为 REST 端点制作服务客户端,使用 JAX-RS 客户端处理 HTTP 请求,并使用 Jackson 来(反)序列化 JSON 实体。为了处理 JSR-310 (Java8) 日期/时间对象,我将com.fasterxml.jackson.datatype:jackson-datatype-jsr310模块作为依赖项添加到服务客户端,但我没有让它工作。

如何配置 JAX-RS 和/或 Jackson 以使用 jsr310 模块?

我使用以下依赖项:

<dependency>
  <groupId>javax.ws.rs</groupId>
  <artifactId>javax.ws.rs-api</artifactId>
  <version>${jax-rs.version}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>${jackson.version}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>${jackson.version}</version>
</dependency>

我不想让服务客户端(作为库发布)依赖于任何特定的实现——比如 Jersey,所以我只依赖 JAX-RS API。为了运行我的集成测试,我添加了:

<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-client</artifactId>
  <version>${jersey.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.inject</groupId>
  <artifactId>jersey-hk2</artifactId>
  <version>${jersey.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-json-jackson</artifactId>
  <version>${jersey.version}</version>
  <scope>test</scope>
</dependency>

JAX-RS 客户端的实例化在工厂对象中完成,如下所示:

import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

@Produces
public Client produceClient() {
    return ClientBuilder.newClient();
}

典型的 DTO 如下所示:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import java.time.Instant;

import static java.util.Objects.requireNonNull;

@JsonPropertyOrder({"n", "t"})
public final class Message {

    private final String name;
    private final Instant timestamp;

    @JsonCreator
    public Message(@JsonProperty("n") final String name,
                   @JsonProperty("t") final Instant timestamp) {
        this.name = requireNonNull(name);
        this.timestamp = requireNonNull(timestamp);
    }

    @JsonProperty("n")
    public String getName() {
        return this.name;
    }

    @JsonProperty("t")
    public Instant getTimestamp() {
        return this.timestamp;
    }

    // equals(Object), hashCode() and toString()
}

请求是这样完成的:

import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;

public final class Gateway {

    private final WebTarget endpoint;

    public Message postSomething(final Something something) {
        return this.endpoint
                .request(MediaType.APPLICATION_JSON_TYPE)
                .post(Entity.json(something), Message.class);
    }

    // where Message is the class defined above and Something is a similar DTO
}

JSON 序列化和反序列化适用于字符串、整数、BigIntegers、列表等。但是,当我System.out.println(gateway.postSomthing(new Something("x", "y"));在测试中执行类似操作时,会出现以下异常:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.Instant` (no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('Fri, 22 Sep 2017 10:26:52 GMT')
 at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 562] (through reference chain: Message["t"])
        at org.example.com.ServiceClientTest.test(ServiceClientTest.java:52)
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of `java.time.Instant` (no Creators, like default construct, exist): no String-argument constructor/factory method to deserialize from String value ('Fri, 22 Sep 2017 10:26:52 GMT')
 at [Source: (org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$UnCloseableInputStream); line: 1, column: 562] (through reference chain: Message["t"])
        at org.example.com.ServiceClientTest.test(ServiceClientTest.java:52)

从中我得出结论,杰克逊不知道如何将字符串反序列化为即时。我找到了关于这个主题的博客和 SO 问题,但我没有找到关于如何使其工作的明确解释。

请注意,我希望服务客户端能够处理"Fri, 22 Sep 2017 10:26:52 GMT"以及之类的日期字符串"2017-09-22T10:26:52.123Z",但我希望它始终序列化为 ISO 8601 日期字符串。

谁能解释一下如何将反序列化变成Instant作品?

4

2 回答 2

8

在示例代码中,您当前依赖 jersey-media-json-jackson。您可能会更好地依赖 Jackson 的 JAX-RS JSON,因为您可以使用标准 JAX-RS API(当然还有 Jackson API)来配置 Jackson 映射器。

    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>${jackson.version}</version>
    </dependency>

删除 jersey-media-json-jackson 并添加 jackson-jaxrs-json-provider 依赖项后,您可以配置 JacksonJaxbJsonProvider 并将其注册到生成客户端的类中:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

import javax.ws.rs.Produces;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

import static com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.DEFAULT_ANNOTATIONS;

public class ClientProducer {

    private JacksonJsonProvider jsonProvider;

    public ClientProducer() {
        // Create an ObjectMapper to be used for (de)serializing to/from JSON.
        ObjectMapper objectMapper = new ObjectMapper();
        // Register the JavaTimeModule for JSR-310 DateTime (de)serialization
        objectMapper.registerModule(new JavaTimeModule());
        // Configure the object mapper te serialize to timestamp strings.
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        // Create a Jackson Provider
        this.jsonProvider = new JacksonJaxbJsonProvider(objectMapper, DEFAULT_ANNOTATIONS);
    }

    @Produces
    public Client produceClient() {

        return ClientBuilder.newClient()
                // Register the jsonProvider
                .register(this.jsonProvider);
    }
}

希望这可以帮助。

于 2017-09-22T19:48:55.147 回答
3

您可以ObjectMapperContextResolver. Jackson JAX-RS 提供程序将查找此解析器并从中获取ObjectMapper

@Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
    private final ObjectMapper mapper;

    public ObjectMapperResolver() {
        mapper = new ObjectMapper();
        // configure mapper
    }

    @Override
    public ObjectMapper getContext(Class<?> cls) {
        return mapper;
    }
}

然后只需像在 JAX-RS 应用程序中注册任何其他提供程序或资源一样注册解析器。

于 2018-05-05T02:39:21.113 回答