1. 问题现象
Result<List<UserInfo>> result = restTemplate
.exchange("http://127.0.0.1:8080/user/t/save", HttpMethod.POST, new HttpEntity<>(params), new ParameterizedTypeReference<Result<List<UserInfo>>>() {
}).getBody();
复制代码
private Date createTime;
复制代码
通过RestTemplate请求接口,以对象接收返回报文,其中以Date接收报文中时间格式字符串。
org.springframework.web.client.RestClientException: Error while extracting response for type [com.example.demo.bean.Result<java.util.List<com.example.demo.domain.UserInfo>>] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.Date` from String "2021-05-27 09:41:27": not a valid representation (error: Failed to parse Date value '2021-05-27 09:41:27': Cannot parse date "2021-05-27 09:41:27": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null)); nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2021-05-27 09:41:27": not a valid representation (error: Failed to parse Date value '2021-05-27 09:41:27': Cannot parse date "2021-05-27 09:41:27": while it seems to fit format 'yyyy-MM-dd'T'HH:mm:ss.SSSX', parsing fails (leniency? null))
at [Source: (PushbackInputStream); line: 1, column: 51] (through reference chain: com.example.demo.bean.Result["data"]->java.util.ArrayList[0]->com.example.demo.domain.UserInfo["createTime"])
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:120)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:998)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:981)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:741)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:612)
复制代码
可以看到由于时间格式不对,导致jackson序列化失败。
对于在Spring boot中如何处理时间格式序列化,我们知道有2种方式:
1.在application.yml
配置全局jackson时间格式
spring:
jackson:
#日期格式化
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
复制代码
2.在时间成员变量上加上@DateTimeFormat
和@JsonFormat
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date createTime;
复制代码
出现此问题后,首先检查了application.yml
中的配置,确认正常。
然后尝试在字段上添加格式注解,测试调用,应用正常。但是需要在每个时间字段上加注解这种方式,太费力了,显然不是我们想要的。
2. 排查过程
根据以上现象,初步怀疑,RestTemplate处理参数时,yml中的配置没有生效。
根据异常堆栈信息,定位到HttpMessageConverterExtractor
,打上断点,DEBUG跟踪
1. 猜测验证
可以看到此对象有objectMapper。
那我们直接查看ObjectMapper中的DateFormat,可以看到属性都是默认值,我们在yml中的配置没有生效。
2. jasckson配置加载
通过上述过程,我们已经看到在处理参数时,jackson没有使用我们在yml中的配置信息。因此,怀疑应用在启动时,jackson参数没有加载。找到启动类JacksonAutoConfiguration
, 发现由Jackson2ObjectMapperBuilder
创建ObjectMapper,找到JacksonProperties
打上断点,重启应用调试。
发现配置确实是加载了,不是yml配置的问题,但是MappingJackson2HttpMessageConverter
使用时,配置没有生效。
重新查看MappingJackson2HttpMessageConverter
,发现有2个构造函数,都打上断点,重启应用调试发现2种构造函数,都执行了且执行了多次。怀疑该对象不是单例的,RestTemplate有自己独有的。查看RestTemplate,发现确实如此。
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}
复制代码
3. 问题解决
现在可以确定是RestTemplate
的问题,查看工程中RestTemplate
的注册方式,
发现
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
复制代码
虽然笔者不了解RestTemplate
,但是在Spring Boot工程中此类配置一般是通过Builder进行构造的,直接new对象,很可能对象属性都是走默认值,无法读取工程配置。经查询,RestTemplate
正确注册方式如下
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
复制代码
重启应用验证,正常。
4. 新问题
确认到问题原因,并找到解决方式,把所有工程的RestTemplate
构造方式都修改了,但是没想到,引起了新的问题。有同事在反馈,GateWay启动失败。
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method restTemplate in com.example.demo.config.RestTemplateConfig required a bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.boot.web.client.RestTemplateBuilder' in your configuration.
复制代码
报错信息,提示找不到RestTemplateConfig
。打开RestTemplateAutoConfiguration
static class NotReactiveWebApplicationCondition extends NoneNestedConditions {
NotReactiveWebApplicationCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnWebApplication(type = Type.REACTIVE)
private static class ReactiveWebApplication {
}
}
复制代码
发现ReactiveWebApplication不加载此类,我们知道Spring Boot分mvc(NoReactive)和webflux(Reactive),查看GateWay工程pom文件,确认有webflux依赖。重新检查应用工程,发现依赖中也有webflux,但是可以正常运行。
经测试:有web和webflux,工程是NoReactive。只有webflux才是Reactive。
而webflux工程接口调用应用使用WebClient
。
3. 总结
-
使用不了解的API时应当按照规范使用。
-
引入不了解的api时应当对各个场景做充足测试。