WebClient自定义ObjectMapper解决Instant反序列化失败问题
解决Spring Boot WebClient调用时Instant类型JSON解码失败问题
看起来你遇到的是WebClient默认Jackson配置不识别自定义日期格式的问题——虽然实体类加了@JsonFormat,但WebClient自带的ObjectMapper并没有用上这个配置,导致无法把"Feb 19, 2020 5:30:00 PM"这种格式的字符串反序列化为Instant。下面我一步步给你解决思路和代码示例:
1. 先确认实体类的注解配置是否正确
首先检查你的TalonInfo类,确保registrationTime字段的@JsonFormat格式完全匹配接口返回的日期字符串,还要指定时区和本地化(因为是英文月份缩写):
import com.fasterxml.jackson.annotation.JsonFormat; import java.time.Instant; public class TalonInfo { // 其他字段... @JsonFormat( shape = JsonFormat.Shape.STRING, pattern = "MMM dd, yyyy hh:mm:ss a", locale = "en_US", timezone = "GMT" // 这里要和接口返回的日期时区一致 ) private Instant registrationTime; // getter、setter方法 }
2. 自定义WebClient的ObjectMapper配置
WebClient默认的编解码器不会自动复用你项目中自定义的ObjectMapper,所以我们需要手动替换它的Jackson编解码器,让它使用能识别这个日期格式的配置。
方式一:创建独立的自定义ObjectMapper并注入WebClient
写一个配置类,专门为WebClient定制ObjectMapper:
import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import java.io.IOException; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Locale; @Configuration public class WebClientConfig { @Bean public ObjectMapper webClientObjectMapper() { ObjectMapper objectMapper = new ObjectMapper(); // 注册Java 8时间类型处理模块 objectMapper.registerModule(new JavaTimeModule()); // 启用注解支持,让实体类的@JsonFormat生效 objectMapper.enable(com.fasterxml.jackson.databind.MapperFeature.USE_ANNOTATIONS); // 额外添加Instant的自定义反序列化器(如果@JsonFormat不生效的话可以用这个兜底) SimpleModule module = new SimpleModule(); module.addDeserializer(Instant.class, new JsonDeserializer<Instant>() { @Override public Instant deserialize(com.fasterxml.jackson.core.JsonParser p, DeserializationContext ctxt) throws IOException { String dateStr = p.getText(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd, yyyy hh:mm:ss a") .withLocale(Locale.ENGLISH) .withZone(ZoneId.of("GMT")); return Instant.from(formatter.parse(dateStr)); } }); objectMapper.registerModule(module); return objectMapper; } @Bean public WebClient customWebClient(WebClient.Builder webClientBuilder, ObjectMapper webClientObjectMapper) { // 构建自定义的ExchangeStrategies,替换默认的Jackson编解码器 ExchangeStrategies strategies = ExchangeStrategies.builder() .codecs(configurer -> { configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(webClientObjectMapper)); configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(webClientObjectMapper)); }) .build(); return webClientBuilder .exchangeStrategies(strategies) .baseUrl("你的接口基础地址") // 替换成实际的接口域名/IP .build(); } }
方式二:复用Spring Boot自动配置的ObjectMapper
如果你项目中已经有全局的ObjectMapper配置,可以直接复用它,只需要修改WebClient的编解码器:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; @Configuration public class WebClientConfig { @Autowired private ObjectMapper objectMapper; @Bean public WebClient customWebClient(WebClient.Builder webClientBuilder) { ExchangeStrategies strategies = ExchangeStrategies.builder() .codecs(configurer -> { configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper)); configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper)); }) .build(); return webClientBuilder .exchangeStrategies(strategies) .baseUrl("你的接口基础地址") .build(); } }
这种方式需要确保你的全局ObjectMapper已经注册了JavaTimeModule并启用了注解支持。
3. 在服务类中使用自定义的WebClient
修改你的服务实现类,注入我们配置好的WebClient:
import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; @Service public class TalonInfoService { private final WebClient webClient; // 构造注入自定义WebClient public TalonInfoService(WebClient customWebClient) { this.webClient = customWebClient; } public TalonInfo getInfo() { return webClient.get() .uri("/getinfo") // 接口的具体路径 .retrieve() .bodyToMono(TalonInfo.class) .block(); // 现在执行block()应该不会再出现解码错误了 } }
4. 测试类示例
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest public class TalonInfoServiceTest { @Autowired private TalonInfoService talonInfoService; @Test public void testGetInfoSuccess() { TalonInfo talonInfo = talonInfoService.getInfo(); assertNotNull(talonInfo); assertNotNull(talonInfo.getRegistrationTime()); System.out.println("解析后的时间:" + talonInfo.getRegistrationTime()); } }
补充:待反序列化的JSON示例
确保接口返回的JSON格式如下(和你的实体类字段对应):
{ "id": 123, "registrationTime": "Feb 19, 2020 5:30:00 PM", "name": "test-talon" }
内容的提问来源于stack exchange,提问作者Анатолий Оглоблин
相关产品推荐
相关产品推荐

