Imagine uma situação bastante comum: seu aplicativo interage com clientes que estão em fusos horários diferentes. Freqüentemente, você precisa trabalhar com datas e, para que o sistema funcione corretamente, elas são enviadas com o fuso horário do remetente. Ao fazer isso, você precisa:
Quando uma solicitação for recebida, traga a data para o horário do servidor e trabalhe com ela, e também salve-a no banco de dados neste formulário
Em resposta, retorne a data e hora indicando o fuso horário do servidor
Para fazer isso, o Spring fornece um mecanismo conveniente para escrever serialização e desserialização customizadas. Sua principal vantagem é a capacidade de mover as conversões de data (e outros tipos de dados) para uma classe de configuração separada e não chamar métodos de conversão todas as vezes no código-fonte.
Desserialização
Para que o Spring entenda que é nossa classe que precisa ser usada para (des) serialização, ela deve ser marcada com a anotação @JsonComponent
Bem, para manter o código o mais conciso possível, usarei uma classe estática interna, que deve ser herdada JsonDeserializer
e parametrizada com o tipo de dados de que precisamos. Uma vez que JsonDeserializer
é uma classe abstrata, precisamos substituir seu método abstratodeserialize()
@JsonComponent
public class CustomDateSerializer {
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
return null;
}
}
}
A partir dos parâmetros do método, obtemos a string passada pelo cliente, verificamos se há nulo e obtemos um objeto de classe a partir dela ZonedDateTime
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) {
String date = jsonParser.getText();
if (date.isEmpty() || isNull(date) {
return null;
}
ZonedDateTime userDateTime = ZonedDateTime.parse(date);
}
, userDateTime
withZoneSameInstant()
. LocalDateTime
, , , . , .
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String date = jsonParser.getText();
if (date.isEmpty()) {
return null;
}
try {
ZonedDateTime userDateTime = ZonedDateTime.parse(date);
ZonedDateTime serverTime = userDateTime.withZoneSameInstant(ZoneId.systemDefault());
return serverTime.toLocalDateTime();
} catch (DateTimeParseException e) {
try {
return LocalDateTime.parse(date);
} catch (DateTimeParseException ex) {
throw new IllegalArgumentException("Error while parsing date", ex);
}
}
}
}
, UTC+03. , 2021-01-21T22:00:00+07:00
,
public class Subscription {
private LocalDateTime startDate;
// standart getters and setters
}
@RestController
public class TestController {
@PostMapping
public void process(@RequestBody Subscription subscription) {
// startDate subscription 2021-01-21T18:00
}
}
. JsonSerializer
, serialize()
null, .
public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (isNull(localDateTime)) {
return;
}
OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));
jsonGenerator.writeString(timeUtc.toString());
}
}
? ? . , , , UTC+00. , id . ZoneOffset
, UTC+03, : 2021-02-21T18:00+03:00.
UTC+00, 2021-02-21T18:00Z
Como estamos trabalhando com uma string, não será difícil para nós mudar um pouco o código para que sempre tenhamos a data no mesmo formato na saída. Vamos declarar duas constantes - uma delas será igual ao id padrão UTC + 00, e a segunda - que queremos dar ao cliente, e adicionar uma verificação - se o horário do servidor estiver no fuso horário zero, então irá substituí-lo Z
por +00:00
. Como resultado, nosso serializador se parecerá com este
public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private static final String UTC_0_OFFSET_ID = "Z";
private static final String UTC_0_TIMEZONE = "+00:00";
@Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (!isNull(localDateTime)) {
String date;
OffsetDateTime timeUtc = localDateTime.atOffset(ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now()));
if (UTC_0_OFFSET_ID.equals(timeUtc.getOffset().getId())) {
date = timeUtc.toString().replace(UTC_0_OFFSET_ID, UTC_0_TIMEZONE);
} else {
date = timeUtc.toString();
}
jsonGenerator.writeString(date);
}
}
}
Total
Graças aos mecanismos integrados de Spring, fomos capazes de converter automaticamente a data e hora no formato necessário, sem nenhuma chamada de método explícita no código
O código-fonte completo pode ser visto aqui