Джексон (де) сериализация даты / времени Java8 клиентом JAX-RS
Я делаю Serivce-клиент для конечной точки REST, использую JAX-RS-клиент для HTTP-запросов и Джексона для (де) сериализации 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>
Я не хочу, чтобы сервисный клиент (который выпускается как библиотека) зависел от какой-либо конкретной реализации - например, от Джерси, поэтому я полагаюсь только на API JAX-RS. Для запуска интеграционных тестов я добавил:
<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-сериализация и десериализация отлично работает для Strings, Ints, BigIntegers, Lists и т. Д. Однако, когда я делаю что-то вроде 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)
Из чего я заключаю, что Джексон не знает, как десериализовать строки в инстансы. Я нашел блоги и ТАК вопросы по этой теме, но я не нашел четкого объяснения того, как заставить это работать.
Обратите внимание, что я бы хотел, чтобы клиент службы обрабатывал такие строки даты, как "Fri, 22 Sep 2017 10:26:52 GMT"
так же как "2017-09-22T10:26:52.123Z"
, но я хочу, чтобы он всегда сериализовался в строки даты ISO 8601.
Кто может объяснить, как превратить десериализацию в Instant
Работа?
2 ответа
В примере кода вы в настоящее время зависите от jersey-media-json-jackson. Вероятно, вам будет лучше, если вы будете использовать JSON JS-RS JSON от Джексона, поскольку вы можете настроить картограф Джексона с использованием стандартного API JAX-RS (и, конечно, API Джексона).
<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 и зарегистрировать его в классе, который создает Client:
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);
}
}
Надеюсь это поможет.
Вы можете настроить Джексона ObjectMapper
в ContextResolver
, Поставщик 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.