Spring MVC: Gerenciamento de Formatação de Datas e Perda de Precisão para Tipos Long com HttpMessageConverter

Ao desenvolver aplicações Spring MVC, é comum enfrentar desafios relacionados à serialização e desserialização de dados entre o backend e o frontend. Dois problemas recorrentes são a perda de precisão em valores numéricos do tipo Long e a formatação inadequada de objetos LocalDateTime durante a conversão para JSON.

Por exemplo, um identificador Long com um valor grande como 17283940582736451 pode ser recebido pelo frontend JavaScript como 17283940582736450, devido às limitações de representação de números inteiros em JavaScript. Da mesma forma, objetos LocalDateTime frequentemente são serializados como timestamps numéricos, em vez de um formato de data e hora legível como yyyy-MM-dd HH:mm:ss. Esses problemas são gerenciados internamente pelo HttpMessageConverter do Spring MVC.

Entendendo o HttpMessageConverter

No ecossistema Spring MVC, o HttpMessageConverter é um componente crucial que atua como tradutor entre as requisições/respostas HTTP e os objetos Java. Quando um método de controller utiliza anotações como @RequestBody (para transformar o corpo da requisição em um objeto Java) ou @ResponseBody (para serializar um objeto Java no corpo da resposta), o Spring invoca uma implementação do HttpMessageConverter.

Implementações Comuns

O Spring oferece várias implementações prontas, sendo as mais utilizadas:

  • StringHttpMessageConverter: Lida com a conversão de e para String.
  • MappingJackson2HttpMessageConverter: A base para a manipulação de JSON, utilizando a biblioteca Jackson para serialização e desserialização de objetos Java.

Configuração Unificada para Formatação e Precisão de Long

Para resolver as questões de formatação de LocalDateTime e a perda de precisão de Long, é prática comum personalizar o ObjectMapper da Jackson e integrá-lo ao MappingJackson2HttpMessageConverter.

Passo 1: Criando um Mapeador de Objetos Personalizado

Primeiro, definimos uma classe que estende ObjectMapper para aplicar regras de serialização específicas. Este customizador desabilita a falha em propriedades desconhecidas e configura serilaizadores para LocalDateTime e Long.


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;

public class CustomJsonMapper extends ObjectMapper {

   public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

   public CustomJsonMapper() {
       super();
       // Configura para não falhar ao encontrar propriedades desconhecidas
       this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

       // Cria um módulo simples para registrar serializadores e desserializadores
       SimpleModule dateTimeAndLongModule = new SimpleModule();

       // Serializa LocalDateTime para o formato yyyy-MM-dd HH:mm:ss
       dateTimeAndLongModule.addSerializer(LocalDateTime.class, 
           new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)));
       // Desserializa LocalDateTime do formato yyyy-MM-dd HH:mm:ss
       dateTimeAndLongModule.addDeserializer(LocalDateTime.class,
           new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)));

       // Serializa Long para String para evitar perda de precisão no JavaScript
       dateTimeAndLongModule.addSerializer(Long.class, ToStringSerializer.instance);

       // Registra o módulo no ObjectMapper
       this.registerModule(dateTimeAndLongModule);
   }
}

Passo 2: Estendendo os Conversores de Mensagem no Spring MVC

Em uma classe de configuração que estende WebMvcConfigurationSupport (ou implementa WebMvcConfigurer e sobrescreve o método), podemos adicionar nosso conversor personalizado à lista de conversores do Spring.


import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {

   /**
    * Estende os conversores de mensagem do framework Spring MVC.
    * Adiciona um HttpMessageConverter personalizado para JSON.
    */
   @Override
   protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
       // 1. Instancia um novo conversor JSON
       MappingJackson2HttpMessageConverter customJsonConverter = new MappingJackson2HttpMessageConverter();
       // 2. Associa nosso ObjectMapper personalizado a ele
       customJsonConverter.setObjectMapper(new CustomJsonMapper());
       // 3. Adiciona o conversor personalizado ao início da lista para garantir sua prioridade
       converters.add(0, customJsonConverter);
   }
}

Princípios de Funcionamento dos Conversores

Por que extendMessageConverters em vez de configureMessageConverters?

  • configureMessageConverters(List<HttpMessageConverter<?>> converters): Este método substitui completamente a lista padrão de conversores do Spring. Se você o usar, precisará reconfigurar manualmente todos os conversores padrão que deseja manter, o que pode ser trabalhoso.
  • extendMessageConverters(List<HttpMessageConverter<?>> converters): Este método permite que você modifique a lista de conversores padrão. Ao usar converters.add(0, customJsonConverter), você insere seu conversor no início da lista, garantindo que ele seja considerado antes dos conversores padrão para tipos de mídia compatíveis.

Mecanismo de Negociação de Conteúdo

O Spring MVC determina qual HttpMessageConverter utilizar através de um processo de negociação de conteúdo. Os pontos chave para essa decisão são:

  1. Header Content-Type da Requisição: Para requisições (@RequestBody), o Spring verifica o Content-Type no cabeçalho da requisição (ex: application/json) e seleciona um conversor que possa ler esse tipo.
  2. Header Accept da Requisição: Para respostas (@ResponseBody), o Spring analisa o Accept no cabeçalho da requisição (ex: application/xml) para encontrar um conversor que possa escrever o tipo de mídia solicitado.
  3. Tipo de Retorno do Método Controller: O tipo do objeto Java retornado pelo método do controller é avaliado para garantir que o conversor selecionado seja capaz de serializar esse tipo.
  4. Ordem de Prioridade: Os conversores são iterados na ordem em que estão na lista de conversores configurados. O primeiro conversor que é capaz de lidar com o tipo de mídia e o tipo de objeto é selecionado. A inserção do seu conversor personalizado na posição 0 assegura que ele tenha a maior prioridade.

Tags: Spring MVC HttpMessageConverter Jackson JSON LocalDateTime

Publicado em 5-31 04:09 por Thomas