feign请求返回值反序列LocalDateTime异常记录
前言
最近项目组用feign调用远程服务,消费端报了如下一个异常
从异常信息可以得出localdatime反序列化出了异常,而这个异常又是因为jackson无法处理导致。因此我们可以为jackson的ObjectMapper适配一下
解决方法
1、在pom.xml引入
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
<version>${jackson.version}</version>
</dependency>
注: jackson-datatype-jsr310这是用来支持jsr310规范的时间,jackson-datatype-jdk8用来支持新的特定于JDK8的类型,例如Optional
2、替换默认的ObjectMapper
@Configuration
public class LocalDateTimeConfig {
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());
objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());
return objectMapper;
}
疑问点:为什么替换了默认的ObjectMapper后,feign就可以处理LocalDateTime
答案就在
@Configuration(proxyBeanMethods = false)
public class FeignClientsConfiguration {
@Autowired
private ObjectFactory<HttpMessageConverters> messageConverters;
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
return new OptionalDecoder(
new ResponseEntityDecoder(new SpringDecoder(this.messageConverters)));
}
}
而messageConverters默认的转换器是根据HttpMessageConvertersAutoConfiguration而来
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class })
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class,
JsonbHttpMessageConvertersConfiguration.class })
public class HttpMessageConvertersAutoConfiguration {
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
ObjectProvider具有延迟加载的功能,会根据实际情况加载。springboot的web模块默认会引入Jackson相关包。官网上有这么一段话
这个就说明默认的HttpMessageConverter为MappingJackson2HttpMessageConverter,而MappingJackson2HttpMessageConverter会利用objectMapper来进行序列化和反序列化
上面的的话用代码整理如下
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
@Bean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(messageConverters()));
}
public ObjectFactory<HttpMessageConverters> messageConverters() {
return () -> new HttpMessageConverters(mappingJackson2HttpMessageConverter());
}
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(objectMapper());
}
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());
objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());
return objectMapper;
}
总结
异常信息很重要,源码很重要,官网同样重要
「其他文章」
- 聊聊如何基于spring @Cacheable扩展实现缓存自动过期时间以及即将到期自动刷新
- 聊聊基于docker部署的mysql如何进行数据恢复
- 聊聊如何验证线上的版本是符合预期的版本
- 聊聊如何让你的业务代码具有可扩展性
- spring事务失效的几种场景以及原因
- 聊聊自定义SPI如何与sentinel整合实现熔断限流
- 聊聊如何实现一个支持键值对的SPI
- 聊聊springboot项目如何实现自定义actuator端点
- 聊聊使用lombok @Builder踩到的坑
- 笑话
- 聊聊如何自定义实现maven插件
- 聊聊在idea dubug模式下,动态代理类出现的null现象
- 聊聊基于jdk实现的spi如何与spring整合实现依赖注入
- 笑话
- 聊聊springcloud项目同时存在多个注册中心客户端采坑记
- feign请求返回值反序列LocalDateTime异常记录
- 笑话
- 笑话
- 笑话
- 笑话