关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言
服务端接收参数的形式有多种,可以通过Form
表单的形式接收,也可以使用@RequestBody
来接收Json
参数。但是接收参数过程中,唯独日期类型的参数总是出现解析异常。如下图:

近日,遇到同事求助说通过@RequestBody
接受日期参数异常,如上图。项目框架是我搭建的,当时已经处理了关于日期参数传递的解析问题,还是出现问题有点不可思议。
我们一起来看看,怎么解决!
02 全局日期处理器
搭建框架初期,专门通过@InitBinder
和@ControllerAdvice
处理了日期,作为全局的日期处理器。
java
@Slf4j
@ControllerAdvice
public class GlobalWebBinderHandler {
/**
* 处理web的data数据,这里主要处理form表单的日期
*/
@InitBinder
public void globalWebDataBinder(WebDataBinder binder){
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
Date date = null;
try {
if (StringUtils.isNotBlank(text)) {
if (StringUtils.isNumeric(text)) {
date = Date.from(Instant.ofEpochMilli(Long.parseLong(text)));
}else {
date = DateUtil.parseDate(text);
}
}
} catch (ParseException e) {
log.warn("web 日期参数解析异常,异常格式:{}, 支持的日期格式:{}",
text, JSON.toJSONString(DateUtil.GENERIC_DATE_PATTERNS));
}
setValue(date);
}
});
}
}
@ControllerAdvice
:用来拦截所有的@Controller
控制层@InitBinder
:用来处理Form
表单传递的参数WebDataBinder
:用来映射String
参数为T
,这里只处理了Date
DateUtil.parseDate(text)
是通过DateUtil.GENERIC_DATE_PATTERN
数组,依次解析日期,直到日期被正确解析,都则接收的值为NULL
。
通过@InitBinder
注解的注释可以看到,支持RequestMapping
的所有参数,尤其Form
表单参数。

而从遇到的问题看,这里的配置并没有走到。而是Jackson反序列化时遇到了异常。
03 @RequestBody
同时遇到的异常就是使用@RequestBody
来接收JSON
数据而导致的。这是怎么产生的呢?@RequestBody
是一个 Spring MVC
注解,它的主要作用是将 HTTP 请求体(Body)中的数据,按照特定的格式(如 JSON、XML)绑定到控制器方法的参数上。它通常用于处理 POST
、PUT
、PATCH
等非 GET
请求,这些请求的数据通常放在请求体中。
3.1 完整的解析流程
整个解析过程可以看作一条清晰的流水线,涉及了从 HTTP 请求到 Java 对象转化的每一步。下图清晰地展示了这一核心流程:

详细说明:
接收请求与分发
- ① HTTP 请求到达 :客户端发送一个 HTTP 请求(例如,Content-Type:
application/json
)。 - ②
DispatcherServlet
拦截 :作为前端控制器,DispatcherServlet
会拦截所有请求。 - ③ 查找处理器 :
DispatcherServlet
根据请求 URL,通过HandlerMapping
找到对应的控制器(Controller)和处理方法(HandlerMethod
)。
准备调用处理方法
- ④
HandlerAdapter
接手 :DispatcherServlet
将请求交给RequestMappingHandlerAdapter
来处理。 - ⑤ 解析方法参数 :
HandlerAdapter
开始准备调用目标方法。它需要解析方法的每一个参数,并为每个参数赋值。这时,它会使用一系列HandlerMethodArgumentResolver
(参数解析器)。
识别 @RequestBody
注解
- ⑥ 找到合适的解析器 :
HandlerAdapter
遍历所有参数解析器,当它遇到一个带有@RequestBody
注解的参数时,会找到一个特定的解析器 ------RequestResponseBodyMethodProcessor
。 - ⑦
RequestResponseBodyMethodProcessor
工作 :这个解析器专门负责处理@RequestBody
和@ResponseBody
。
读取请求体与内容协商
- ⑧ 获取输入流 :
RequestResponseBodyMethodProcessor
从HttpServletRequest
中获取请求体的输入流。 - ⑨ 内容协商 :它检查请求的
Content-Type
头(例如application/json
),以确定客户端发送的数据格式。 - ⑩ 选择消息转换器 :根据
Content-Type
,它会从配置好的HttpMessageConverter
列表中选择一个合适的转换器。对于application/json
,默认使用的是MappingJackson2HttpMessageConverter
(如果 Jackson 库在类路径上)。
反序列化与类型转换
- ⑪ 反序列化(核心步骤) :
MappingJackson2HttpMessageConverter
从输入流中读取原始的JSON
字符串,使用 Jackson 的ObjectMapper
来执行反序列化。
数据验证
- ⑫ 执行校验 :如果参数上同时还使用了
@Valid
或@Validated
注解,RequestResponseBodyMethodProcessor
会在此刻触发JSR-303 Bean
验证。
方法调用与返回
- ⑬ 参数绑定完成:此时,一个完整的、填充好数据的 Java 对象已经准备就绪
- ⑭ 调用控制器方法 :
HandlerAdapter
用这个对象作为参数,调用你的控制器方法。 - ⑮ 处理响应 :方法执行完毕后,返回结果,流程进入
@ResponseBody
的序列化流程。
3.2 关键代码追踪

通过跟踪代码,大致的流程如上图。

根据断点调试,我们会发现默认的MessageConverters
有8个,第6个和第7个是专门解析application/json
类型的。由于源码是按照顺序遍历,所以必然会取到第6个。

我们可以看到内存地址是@6766
,验证了我们的猜想。
继续跟踪代码,就发现了我们之前的报错:

因为这里的反序列化框架使用的是Jackson
,而Jackson
默认的日期格式是:yyyy-MM-dd'T'HH:mm:ss.SSSX
04 问题解决
问题原因知道了,解决也就简单了。
4.1 局部解决
我们需要使用Jackson的一个注解:@JsonForma
java
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+08:00")
只需要在日期类型的字段上添加上面的注解,即可解决。一定要增加时区的修正,否则日期就会出现时差。

这种解决方案有其弊端。因为很多实体使用的是逆向工程,随时可能会被替换掉。还有就是这种作为需要为每一个日期字段增加注解。不能全局解决。
4.2 全局配置
Jackson
框架有全局的配置,我们只需要修改全局配置即可解决。
properties
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
4.3 配置其他消息转化器
阿里开源的fajson
或者fasjon2
,本身默认就是yyyy-MM-dd HH:mm:ss
日期格式。我们直接使用阿里的消息转化器器即可。
依赖引入
fasjon2
作为第二代json
处理工具,和第一代的引入方式有一些区别。这里需要引入fastjson2
的扩展包,其中Spring
的版本需要根据项目的版本引入。小编这里使用的是spring6
。
xml
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension-spring6</artifactId>
<version>2.0.45</version>
</dependency>
注册
java
@Configuration
public class BeanConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 这里可以按需指定日期格式
// fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
converter.setFastJsonConfig(fastJsonConfig);
converters.add(0, converter);
}
}
fastJsonConfig
作为消息转化器的配置项,在项目中按需配置即可。从之前的源码来看,消息转化器是按照顺序解析的,我们需要将消自定的FastJsonHttpMessageConverter
加载在前面。或者直接用set
的方式替换默认的消息转化器。
05 小结
@RequestBody
的解析是一个由 DispatcherServlet
-> HandlerAdapter
-> RequestResponseBodyMethodProcessor
-> HttpMessageConverter
协同完成的复杂过程。理解这个流程对于深入掌握 Spring MVC
、高效处理 RESTful API
以及精准定位和解决相关问题至关重要。它完美地体现了 Spring 框架通过组件分工和策略接口提供的强大扩展能力和灵活性。
理解其原理,解决方案也就明朗了。