项目中使用LocalDateTime系列作为DTO中时间的数据类型,但是SpringMVC收到参数后总报错,为了配置全局时间类型转换,尝试了如下处理方式。
注:本文基于Springboot2.x测试,如果无法生效可能是spring版本较低导致的。PS:如果你的Controller中的LocalDate类型的参数啥注解(RequestParam、PathVariable等)都没加,也是会出错的,因为默认情况下,解析这种参数是使用**ModelAttributeMethodProcessor**进行处理,而这个处理器要通过反射实例化一个对象出来,然后再对对象中的各个参数进行convert,但是LocalDate类没有构造函数,无法反射实例化因此会报错!!!
完成目标
- 请求入参为 String(指定格式)转 Date,支持get、post(content-type=application/json)
- 返回数据为Date类型转为指定的日期时间格式字符创
支持Java8 日期 API,如:LocalTime、localDate 和 LocalDateTime
GET请求及POST表单日期时间字符串格式转换
这种情况要和时间作为Json字符串时区别对待,因为前端json转后端pojo底层使用的是Json序列化Jackson工具(HttpMessgeConverter);而时间字符串作为普通请求参数传入时,转换用的是Converter,两者在处理方式上是有区别。
使用自定义参数转换器(Converter)
实现 org.springframework.core.convert.converter.Converter,自定义参数转换器,如下:
@Configurationpublic class DateConverterConfig {@Beanpublic Converter<String, LocalDate> localDateConverter() {return new Converter<String, LocalDate>() {@Overridepublic LocalDate convert(String source) {return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));}};}@Beanpublic Converter<String, LocalDateTime> localDateTimeConverter() {return new Converter<String, LocalDateTime>() {@Overridepublic LocalDateTime convert(String source) {return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}};}}
小结:
GET请求及POST表单方式请求。
- 支持LocalDate等Java8日期API。
点评:以上两个bean会注入到spring mvc的参数解析器(好像叫做ParameterConversionService),当传入的字符串要转为LocalDateTime类时,spring会调用该Converter对这个入参进行转换。
注意:关于自定义的参数转换器 Converter,这里我遇到了一个坑,我再这里详细记录下,本来我的想法是为了代码精简,将上面匿名内部类的写法精简成lambda表达式的方式:
@Bean// @ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDate> localDateConverter() {return source -> LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));}
当我再次启动项目时却出现了异常:
Caused by: java.lang.IllegalArgumentException: Unable to determine source type <S> and target type <T> for your Converter [com.example.demo126.config.MappingConverterAdapter$$Lambda$522/817994751]; does the class parameterize those types?
百思不得其解,在查阅了资料才得知一二:
web项目启动注册requestMappingHandlerAdapter的时候会初始化WebBindingInitializer
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
而ConfigurableWebBindingInitializer需要FormattingConversionService, 而FormattingConversionService会将所有的Converter添加进来,添加的时候需要获取泛型信息:
@Overridepublic void addFormatters(FormatterRegistry registry) {for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {registry.addConverter(converter);}for (GenericConverter converter : getBeansOfType(GenericConverter.class)) {registry.addConverter(converter);}for (Formatter<?> formatter : getBeansOfType(Formatter.class)) {registry.addFormatter(formatter);}}
添加Converter.class 一般是通过接口获取两个泛型的具体类型
public ResolvableType as(Class<?> type) {if (this == NONE) {return NONE;}Class<?> resolved = resolve();if (resolved == null || resolved == type) {return this;}for (ResolvableType interfaceType : getInterfaces()) {ResolvableType interfaceAsType = interfaceType.as(type);if (interfaceAsType != NONE) {return interfaceAsType;}}return getSuperType().as(type);}
Lambda表达式的接口是Converter,并不能得到具体的类型,在窥探了SpringMVC源码后才得知原来如此,既然指导了原因,那解决办法:
- 最简单的方法就是不适用Lambda表达式,还是老老实实的使用匿名内部类,这样就不会存在上述问题
- 或者就是等requestMappingHandlerAdapterbean注册完成之后再添加自己的
converter就不会注册到FormattingConversionService中@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDateTime> localDateTimeConverter() {return source -> LocalDateTime.parse(source, DateTimeUtils.DEFAULT_FORMATTER);}
使用Spring注解
使用spring自带注解@DateTimeFormat(pattern = “yyyy-MM-dd”),如下:
如果使用了自定义参数转化器,Spring会优先使用该方式进行处理,即Spring注解不生效。@DateTimeFormat(pattern = "yyyy-MM-dd")private Date startDate;
那么假如我们使用了自定义参数转换器,但是还是想兼容用yyyy-MM-dd形式接受呢?我们可以把前面的dateConverter改成用正则匹配方式,这样也不失为一种不错的解决方案,示例如下。
/*** 日期正则表达式*/private static final String DATE_REGEX = "[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])";/*** 时间正则表达式*/private static final String TIME_REGEX = "(20|21|22|23|[0-1]\\d):[0-5]\\d:[0-5]\\d";/*** 日期和时间正则表达式*/private static final String DATE_TIME_REGEX = DATE_REGEX + "\\s" + TIME_REGEX;/*** 13位时间戳正则表达式*/private static final String TIME_STAMP_REGEX = "1\\d{12}";/*** 年和月正则表达式*/private static final String YEAR_MONTH_REGEX = "[1-9]\\d{3}-(0[1-9]|1[0-2])";/*** 年和月格式*/private static final String YEAR_MONTH_PATTERN = "yyyy-MM";@Beanpublic Converter<String, Date> dateConverter() {return new Converter<String, Date>() {@SuppressWarnings("NullableProblems")@Overridepublic Date convert(String source) {if (StrUtil.isEmpty(source)) {return null;}if (source.matches(TIME_STAMP_REGEX)) {return new Date(Long.parseLong(source));}DateFormat format;if (source.matches(DATE_TIME_REGEX)) {format = new SimpleDateFormat(DEFAULT_DATETIME_PATTERN);} else if (source.matches(DATE_REGEX)) {format = new SimpleDateFormat(DEFAULT_DATE_FORMAT);} else if (source.matches(YEAR_MONTH_REGEX)) {format = new SimpleDateFormat(YEAR_MONTH_PATTERN);} else {throw new IllegalArgumentException();}try {return format.parse(source);} catch (ParseException e) {throw new RuntimeException(e);}}};}
或者
@Beanpublic Converter<String, Date> dateConverter() {return new Converter<String, Date>() {/** 可对value进行正则匹配,支持日期、时间等多种类型转换* 这里我偷个懒,在匹配Date日期格式时直接使用了 hutool 为我们已经写好的解析工具类,这里就不重复造轮子了* cn.hutool.core.date.DateUtil* @param value* @return*/@SuppressWarnings("NullableProblems")@Overridepublic Date convert(String source) {return DateUtil.parse(source.trim());}};}
注:这里我偷个懒,在匹配Date日期格式时直接使用了 hutool 为我们已经写好的解析工具类,这里就不重复造轮子了,下面的方法同样使用了该工具类,想要在自己的项目中使用该工具类也很简单,在项目pom文件中引入hutool的依赖就可以了,如下:
<!--hu tool 工具类--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.1.3</version></dependency>
小结:
- GET请求及POST表单方式请求,但是需要在每个使用的地方加上
@DateTimeFormat注解。 - 与自定义参数转化器(Converter)不兼容。
- 支持LocalDate等Java8日期API。
使用ControllerAdvice配合initBinder
@ControllerAdvicepublic class GlobalExceptionHandler {@InitBinderprotected void initBinder(WebDataBinder binder) {binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));}});binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN)));}});binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));}});binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {@Overridepublic void setAsText(String text) throws IllegalArgumentException {SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATETIME_PATTERN);try {setValue(formatter.parse(text));} catch (Exception e) {throw new RuntimeException(String.format("Error parsing %s to Date", text));}}});}}
在实际应用中,我们可以把上面代码放到父类中,所有接口继承这个父类,达到全局处理的效果。原理就是与AOP类似,在参数进入handler之前进行转换时使用我们定义的PropertyEditorSupport来处理。
小结:
- GET请求及POST表单方式请求。
-
JSON入参及返回值全局处理
请求类型为:post,content-type=application/json, 后台用@RequestBody接收,默认接收及返回值格式为: yyyy-MM-dd HH:mm:ss
修改 application.yml 文件
在application.propertities文件中增加如下内容:
spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8
支持(content-type=application/json)请求中格式为 yyyy-MM-dd HH:mm:ss的字符串,后台用@RequestBody接收,及返回值date转为
yyyy-MM-dd HH:mm:ss格式string;- 不支持(content-type=application/json)请求中yyyy-MM-dd等类型的字符串转为date;
-
利用Jackson的JSON序列化和反序列化
/*** Jackson序列化和反序列化转换器,用于转换Post请求体中的json以及将对象序列化为返回响应的json*/@Beanpublic Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {return builder -> builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN))).serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))).serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))).serializerByType(Date.class, new DateSerializer(false, new SimpleDateFormat(DEFAULT_DATETIME_PATTERN))).deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN))).deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))).deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))).deserializerByType(Date.class, new DateDeserializers.DateDeserializer(DateDeserializers.DateDeserializer.instance, new SimpleDateFormat(DEFAULT_DATETIME_PATTERN), DEFAULT_DATETIME_PATTERN));}
小结:
支持Content-Type 是application/json的POST请求,请求参数字符串和返回的格式都是
yyyy-MM-dd HH:mm:ss如果请求参数是其他格式,如yyyy-MM-dd字符串则报400 Bad Request异常。支持LocalDate等Java8日期API。
PS:上面的方式是通过配置一个Jackson2ObjectMapperBuilderCustomizerBean完成的,除了这种,也可以通过自定义一个MappingJackson2HttpMessageConverter来实现。
@Configurationpublic class JacksonConfig {/** 默认日期时间格式 */public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";/** 默认日期格式 */public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";/** 默认时间格式 */public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";@Beanpublic MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();ObjectMapper objectMapper = new ObjectMapper();// 忽略json字符串中不识别的属性objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 忽略无法转换的对象objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);// PrettyPrinter 格式化输出objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);// NULL不参与序列化// objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);// 指定时区objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));// 日期类型字符串处理objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT));// java8日期日期处理JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));objectMapper.registerModule(javaTimeModule);converter.setObjectMapper(objectMapper);return converter;}}
以上几种方式都可以实现JSON传参时的全局化配置,更推荐后两种代码中增加配置bean的方式,可以同时支持Date和LocalDate。
JSON入参及返回值局部差异化处理
场景: 假如全局日期时间处理格式为:yyyy-MM-dd HH:mm:ss,但是某个字段要求接收或返回日期yyyy-MM-dd。
方式一 使用@DateTimeFormat和@JsonFormat注解
使用springboot自带的注解@JsonFormat(pattern = “yyyy-MM-dd”),如下所示:
@JsonFormat(pattern = "yyyy-MM-dd", timezone="GMT+8")@DateTimeFormat(pattern = "yyyy-MM-dd")private Date originalDate;
如上所示,可以在字段上增加@DateTimeFormat和@JsonFormat注解,可以分别单独指定该字段的接收和返回的日期格式。
PS:@JsonFormat和@DateTimeFormat注解都不是Spring Boot提供的,在Spring应用中也可以使用。
再次提醒,如果使用了自定义参数转化器(Converter),Spring会优先使用该方式进行处理,即@DateTimeFormat注解不生效。
方式二 自定义序列化器和反序列化器
/*** 日期序列化*/public class DateJsonSerializer extends JsonSerializer<Date> {@Overridepublic void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");jsonGenerator.writeString(dateFormat.format(date));}}/*** 日期反序列化*/public class DateJsonDeserializer extends JsonDeserializer<Date> {@Overridepublic Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {try {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");return dateFormat.parse(jsonParser.getText());} catch (ParseException e) {throw new RuntimeException(e);}}}/*** 使用方式*/@JsonSerialize(using = DateJsonSerializer.class)@JsonDeserialize(using = DateJsonDeserializer.class)private Date releaseDate;
日期时间格式化处理方式完整配置
方式一
@Configurationpublic class DateHandlerConfig {/** 默认日期时间格式 */public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";/** 默认日期格式 */public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";/** 默认时间格式 */public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";/*** LocalDate转换器,用于转换RequestParam和PathVariable参数* `@ConditionalOnBean(name = "requestMappingHandlerAdapter")`: 等requestMappingHandlerAdapter bean注册完成之后* 再添加自己的`converter`就不会注册到`FormattingConversionService`中*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDate> localDateConverter() {return source -> LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT));}/*** LocalDateTime转换器,用于转换RequestParam和PathVariable参数*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDateTime> localDateTimeConverter() {return source -> LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT));}/*** LocalTime转换器,用于转换RequestParam和PathVariable参数*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalTime> localTimeConverter() {return source -> LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT));}/*** Date转换器,用于转换RequestParam和PathVariable参数* 这里关于解析各种格式的日期格式采用了 hutool 的日期解析工具类*/@Beanpublic Converter<String, Date> dateConverter() {return new Converter<String, Date>() {@Overridepublic Date convert(String source) {return DateUtil.parse(source.trim());}};}/*** Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json*/@Beanpublic ObjectMapper objectMapper(){ObjectMapper objectMapper = new ObjectMapper();objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);//LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式JavaTimeModule javaTimeModule = new JavaTimeModule();javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));// Date序列化和反序列化,會導致@JsonFormat失效javaTimeModule.addSerializer(Date.class, new JsonSerializer<>() {@Overridepublic void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);String formattedDate = formatter.format(date);jsonGenerator.writeString(formattedDate);}});javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<>() {@Overridepublic Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {SimpleDateFormat format = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);String date = jsonParser.getText();try {return format.parse(date);} catch (ParseException e) {throw new RuntimeException(e);}}});objectMapper.registerModule(javaTimeModule);return objectMapper;}}
方式二
import cn.hutool.core.util.StrUtil;import com.fasterxml.jackson.databind.deser.std.DateDeserializers;import com.fasterxml.jackson.databind.ser.std.DateSerializer;import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import java.util.Date;@Configurationpublic class DateHandlerConfig {/*** 日期正则表达式*/private static final String DATE_REGEX = "[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])";/*** 时间正则表达式*/private static final String TIME_REGEX = "(20|21|22|23|[0-1]\\d):[0-5]\\d:[0-5]\\d";/*** 日期和时间正则表达式*/private static final String DATE_TIME_REGEX = DATE_REGEX + "\\s" + TIME_REGEX;/*** 13位时间戳正则表达式*/private static final String TIME_STAMP_REGEX = "1\\d{12}";/*** 年和月正则表达式*/private static final String YEAR_MONTH_REGEX = "[1-9]\\d{3}-(0[1-9]|1[0-2])";/*** 年和月格式*/private static final String YEAR_MONTH_PATTERN = "yyyy-MM";/*** DateTime格式化字符串*/private static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";/*** Date格式化字符串*/private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";/*** Time格式化字符串*/private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss";/*** LocalDate转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, LocalDate> localDateConverter() {return new Converter<String, LocalDate>() {@SuppressWarnings("NullableProblems")@Overridepublic LocalDate convert(String source) {if (StrUtil.isEmpty(source)) {return null;}return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN));}};}/*** LocalDateTime转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, LocalDateTime> localDateTimeConverter() {return new Converter<String, LocalDateTime>() {@SuppressWarnings("NullableProblems")@Overridepublic LocalDateTime convert(String source) {if (StrUtil.isEmpty(source)) {//使用hutool工具cn.hutool.core.util.StrUtilreturn null;}return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN));}};}/*** LocalDate转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, LocalTime> localTimeConverter() {return new Converter<String, LocalTime>() {@SuppressWarnings("NullableProblems")@Overridepublic LocalTime convert(String source) {if (StrUtil.isEmpty(source)) {return null;}return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN));}};}/*** Date转换器,用于转换RequestParam和PathVariable参数*/@Beanpublic Converter<String, Date> dateConverter() {return new Converter<String, Date>() {@SuppressWarnings("NullableProblems")@Overridepublic Date convert(String source) {if (StrUtil.isEmpty(source)) {return null;}if (source.matches(TIME_STAMP_REGEX)) {return new Date(Long.parseLong(source));}DateFormat format;if (source.matches(DATE_TIME_REGEX)) {format = new SimpleDateFormat(DEFAULT_DATETIME_PATTERN);} else if (source.matches(DATE_REGEX)) {format = new SimpleDateFormat(DEFAULT_DATE_PATTERN);} else if (source.matches(YEAR_MONTH_REGEX)) {format = new SimpleDateFormat(YEAR_MONTH_PATTERN);} else {throw new IllegalArgumentException();}try {return format.parse(source);} catch (ParseException e) {throw new RuntimeException(e);}}};}/*** Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json*/@Beanpublic Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {return builder -> builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN))).serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))).serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))).serializerByType(Long.class, ToStringSerializer.instance).serializerByType(Date.class, new DateSerializer(false, new SimpleDateFormat(DEFAULT_DATETIME_PATTERN))).deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN))).deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))).deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))).deserializerByType(Date.class, new DateDeserializers.DateDeserializer(DateDeserializers.DateDeserializer.instance, new SimpleDateFormat(DEFAULT_DATETIME_PATTERN), DEFAULT_DATETIME_PATTERN));}// @Bean// public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {// MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();// ObjectMapper objectMapper = new ObjectMapper();// // 指定时区// objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8:00"));// // 日期类型字符串处理// objectMapper.setDateFormat(new SimpleDateFormat(DEFAULT_DATETIME_PATTERN));//// // Java8日期日期处理// JavaTimeModule javaTimeModule = new JavaTimeModule();// javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN)));// javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));// javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));// javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN)));// javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));// javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));// objectMapper.registerModule(javaTimeModule);//// converter.setObjectMapper(objectMapper);// return converter;// }}
项目中使用(前面两种的合并)
package com.ipebg.base.config;import cn.hutool.core.date.DateUtil;import cn.hutool.core.util.StrUtil;import com.fasterxml.jackson.databind.deser.std.DateDeserializers;import com.fasterxml.jackson.databind.ser.std.DateSerializer;import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import java.text.SimpleDateFormat;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.LocalTime;import java.time.format.DateTimeFormatter;import java.util.Date;/*** 全局日期處理類** @author H2014242* 2021/3/15 上午 11:19*/@Configurationpublic class DateHandlerConfig {/*** DateTime格式化字符串*/private static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";/*** Date格式化字符串*/private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";/*** Time格式化字符串*/private static final String DEFAULT_TIME_PATTERN = "HH:mm:ss";/*** LocalDate转换器,用于转换RequestParam和PathVariable参数*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDate> localDateConverter() {return source -> {if (StrUtil.isEmpty(source)) {return null;}return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN));};}/*** LocalDateTime转换器,用于转换RequestParam和PathVariable参数* 不要听信idea的自动提示将代码转化成lambda方式,会报错*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalDateTime> localDateTimeConverter() {return source -> {if (StrUtil.isEmpty(source)) {return null;}return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN));};}/*** LocalDate转换器,用于转换RequestParam和PathVariable参数*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, LocalTime> localTimeConverter() {return source -> {if (StrUtil.isEmpty(source)) {return null;}return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN));};}/*** Date转换器,用于转换RequestParam和PathVariable参数*/@Bean@ConditionalOnBean(name = "requestMappingHandlerAdapter")public Converter<String, Date> dateConverter() {return source -> DateUtil.parse(source.trim());}/*** Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json*/@Beanpublic Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {return builder -> builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN))).serializerByType(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))).serializerByType(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))).serializerByType(Long.class, ToStringSerializer.instance).serializerByType(Date.class, new DateSerializer(false, new SimpleDateFormat(DEFAULT_DATETIME_PATTERN))).deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN))).deserializerByType(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN))).deserializerByType(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN))).deserializerByType(Date.class, new DateDeserializers.DateDeserializer(DateDeserializers.DateDeserializer.instance, new SimpleDateFormat(DEFAULT_DATETIME_PATTERN), DEFAULT_DATETIME_PATTERN));}}
总结
controller方法的参数是通过不同的HandlerMethodArgumentResolver完成解析的。如果参数标注了@RequestBody注解,实际上是通过MappingJackson2HttpMessageConverter的ObjectMapper将传入json格式数据反序列化解析成目标类型的。如果标注了@RequestParam注解,是通过在应用初始化时注入到ConversionService的一个个Converter来实现的。其他的HandlerMethodArgumentResolver也是各有各的用处,大家可以再看看相关代码,以便加深理解。
// 只能处理Date类型参数的接受和返回,且必须为下面配置指定的格式。// 支持 GET请求及POST表单方式请求,支持Content-Type 是application/json的POST请求。// 不支持 LocalDate等Java8日期API。spring.jackson.date-format=yyyy-MM-dd HH:mm:ssspring.jackson.time-zone=GMT+8// @JsonFormat处理Date类型参数的接受和返回,且必须为下面配置指定的格式。// @JsonFormat处理LocalDate等Java8日期API,只能返回的json按照指定的格式,如果没有标注@JsonFormat// 支持 GET请求及POST表单方式请求,支持Content-Type 是application/json的POST请求。// LocalDate的返回格式为 "2021-04-07",LocalDateTime的返回格式为"2021-04-07T16:53:56.525"@JsonFormat(pattern = "yyyy-MM-dd", timezone="GMT+8")private Date releaseDate;对于参数而言,org.springframework.core.convert.converter.Converter<S, T>的优先级高于@DateTimeFormat,也就是定义了对应时间类型的转换器,@DateTimeFormat不再起作用,格式化以转换器为准;对于RequestBody和返回结果,@JsonFormat的优先级高于org.springframework.http.converter.HttpMessageConverter<T>,在定义了MappingJackson2HttpMessageConverter对时间的序列化与反序列化的情况下,如果Field注解了@JsonFormat且格式不一致,以@JsonFormat注解格式为准
源码剖析
在了解完怎么样进行全局设置后,接下来我们通过debug源码来深入剖析一下Spring MVC是如何进行参数绑定的。
@RequestMapping("/date")public DateEntity getDate(LocalDate date,LocalDateTime dateTime,Date originalDate,DateEntity dateEntity) {System.out.printf("date=%s, dateTime=%s, originalDate=%s \n", date, dateTime, originalDate);return dateEntity;}
以下是收到请求后的方法调用栈的一些关键方法:
// DispatcherServlet处理请求doService:943, DispatcherServlet// 处理请求doDispatch:1040, DispatcherServlet// 生成调用链(前处理、实际调用方法、后处理)handle:87, AbstractHandlerMethodAdapterhandleInternal:793, RequestMappingHandlerAdapter// 反射获取到实际调用方法,准备开始调用invokeHandlerMethod:879, RequestMappingHandlerAdapterinvokeAndHandle:105, ServletInvocableHandlerMethod// 关键步骤,从这里开始处理请求参数invokeForRequest:134, InvocableHandlerMethodgetMethodArgumentValues:167, InvocableHandlerMethodresolveArgument:121, HandlerMethodArgumentResolverComposite
下面我们从关键的invokeForRequest:134, InvocableHandlerMethod处开始分析,源码如下
// InvocableHandlerMethod.java@Nullablepublic Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 这里完成参数的转换,得到的是转换后的值Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);if (logger.isTraceEnabled()) {logger.trace("Arguments: " + Arrays.toString(args));}// 反射调用,真正开始执行方法return doInvoke(args);}// 具体实现protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {// 获取当前handler method的方法参数数组,封装了入参信息,比如类型、泛型等MethodParameter[] parameters = getMethodParameters();if (ObjectUtils.isEmpty(parameters)) {return EMPTY_ARGS;}// 该数组用来存放从MethodParameter转换后的结果Object[] args = new Object[parameters.length];for (int i = 0; i < parameters.length; i++) {MethodParameter parameter = parameters[i];parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);args[i] = findProvidedArgument(parameter, providedArgs);if (args[i] != null) {continue;}// resolvers是定义的成员变量,HandlerMethodArgumentResolverComposite类型,是各式各样的HandlerMethodArgumentResolver的集合。这里来判断一下是否存在支持当前方法参数的参数处理器if (!this.resolvers.supportsParameter(parameter)) {throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));}try {// 调用HandlerMethodArgumentResolverComposite来处理参数,下面会重点看一下内部的逻辑args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);}catch (Exception ex) {......}}return args;}
下面需要进入HandlerMethodArgumentResolverComposite#resolveArgument方法源码里面。
// HandlerMethodArgumentResolverComposite.java@Override@Nullablepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 这里来获取匹配当前方法参数的参数解析器HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);if (resolver == null) {throw new IllegalArgumentException("Unsupported parameter type [" +parameter.getParameterType().getName() + "]. supportsParameter should be called first.");}// 调用真正的参数解析器来处理参数并返回return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);}// 获取匹配当前方法参数的参数解析器@Nullableprivate HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {// 首先从缓存中查询是否有适配当前方法参数的参数解析器,首次进入是没有的HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);if (result == null) {for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {// 逐个遍历argumentResolvers这个list里的参数解析器来判断是否支持if (resolver.supportsParameter(parameter)) {result = resolver;this.argumentResolverCache.put(parameter, result);break;}}}return result;}
argumentResolvers里一共有26个参数解析器,下面罗列一下常见的。
this.argumentResolvers = {LinkedList@6072} size = 260 = {RequestParamMethodArgumentResolver@6098}1 = {RequestParamMapMethodArgumentResolver@6104}2 = {PathVariableMethodArgumentResolver@6111}3 = {PathVariableMapMethodArgumentResolver@6112}......7 = {RequestResponseBodyMethodProcessor@6116}8 = {RequestPartMethodArgumentResolver@6117}9 = {RequestHeaderMethodArgumentResolver@6118}10 = {RequestHeaderMapMethodArgumentResolver@6119}......14 = {RequestAttributeMethodArgumentResolver@6123}15 = {ServletRequestMethodArgumentResolver@6124}......24 = {RequestParamMethodArgumentResolver@6107}25 = {ServletModelAttributeMethodProcessor@6133}
所有的参数解析器都实现了HandlerMethodArgumentResolver接口。
public interface HandlerMethodArgumentResolver {// 上面用到用来判断当前参数解析器是否支持给定的方法参数boolean supportsParameter(MethodParameter parameter);// 解析给定的方法参数并返回@NullableObject resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;}
到这里我们整理一下思路,对方法参数的解析都是通过逐个遍历找到合适的HandlerMethodArgumentResolver来完成的。比如,如果参数上标注了@RequestParam或者@RequestBody或者@PathVariable注解,SpringMVC会用不同的参数解析器来解析。下面挑一个最常用的RequestParamMethodArgumentResolver来深入分析一下详细的解析流程。
RequestParamMethodArgumentResolver继承自AbstractNamedValueMethodArgumentResolver,AbstractNamedValueMethodArgumentResolver实现了HandlerMethodArgumentResolver接口的resolveArgument方法。
// AbstractNamedValueMethodArgumentResolver.java@Override@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {// 解析出传入的原始值,作为下面方法的参数Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);......if (binderFactory != null) {// 创建 DataBinderWebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);try {// 通过DataBinder进行参数绑定,参数列表:原始值,目标类型,方法参数arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);}......}handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);return arg;}// DataBinder.java@Override@Nullablepublic <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable MethodParameter methodParam) throws TypeMismatchException {// 调用子类的convertIfNecessary方法,这里的具体实现是TypeConverterSupportreturn getTypeConverter().convertIfNecessary(value, requiredType, methodParam);}// TypeConverterSupport.java@Override@Nullablepublic <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable MethodParameter methodParam) throws TypeMismatchException {// 调用重载的convertIfNecessary方法,通过MethodParameter构造了类型描述符TypeDescriptorreturn convertIfNecessary(value, requiredType,(methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));}// convertIfNecessary方法@Nullable@Overridepublic <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");try {// 调用TypeConverterDelegate的convertIfNecessary方法return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);}......}
接下来进入TypeConverterDelegate的源码。
// TypeConverterDelegate.java@Nullablepublic <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {// 查找是否有适合需求类型的自定义的PropertyEditor。还记得上面的 使用@ControllerAdvice配合@initBinder 那一节吗,如果有按那样配置,这里就会找到PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);ConversionFailedException conversionAttemptEx = null;// 查找到类型转换服务 ConversionServiceConversionService conversionService = this.propertyEditorRegistry.getConversionService();// 关键判断,如果没有PropertyEditor 就使用ConversionServiceif (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {try {// #1,类型转换服务转换完成后就返回,下面会详细解释return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);}catch (ConversionFailedException ex) {// fallback to default conversion logic belowconversionAttemptEx = ex;}}}Object convertedValue = newValue;// 关键判断,如果有PropertyEditor就使用PropertyEditorif (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {......// 由editor完成转换convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);}boolean standardConversion = false;if (requiredType != null) {// Try to apply some standard type conversion rules if appropriate.if (convertedValue != null) {if (Object.class == requiredType) {return (T) convertedValue;}// 下面是数组、集合类型属性的处理,这里会遍历集合元素,递归调用convertIfNecessary转化,再收集处理结果else if (requiredType.isArray()) {// Array required -> apply appropriate conversion of elements.if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);}return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());}else if (convertedValue instanceof Collection) {// Convert elements to target type, if determined.convertedValue = convertToTypedCollection((Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);standardConversion = true;}else if (convertedValue instanceof Map) {// Convert keys and values to respective target type, if determined.convertedValue = convertToTypedMap((Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);standardConversion = true;}if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {convertedValue = Array.get(convertedValue, 0);standardConversion = true;}if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {// We can stringify any primitive value...return (T) convertedValue.toString();}else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {......}else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {convertedValue = NumberUtils.convertNumberToTargetClass((Number) convertedValue, (Class<Number>) requiredType);standardConversion = true;}}else {// convertedValue == null,空值处理if (requiredType == Optional.class) {convertedValue = Optional.empty();}}......}// 异常处理if (conversionAttemptEx != null) {if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {throw conversionAttemptEx;}logger.debug("Original ConversionService attempt failed - ignored since " +"PropertyEditor based conversion eventually succeeded", conversionAttemptEx);}return (T) convertedValue;}
假如我们配置了自定义的Converter,会进入#1的分支,由ConversionService进行类型转换,以其子类GenericConversionService为例。
// GenericConversionService.java@Override@Nullablepublic Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {......// 从缓存中找到匹配类型的conveter,以LocalDateTime为例,会找到我们自定义的localDateTimeConverterGenericConverter converter = getConverter(sourceType, targetType);if (converter != null) {// 通过工具方法调用真正的converter完成类型转换。至此,完成了源类型到目标类型的转换Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);return handleResult(sourceType, targetType, result);}return handleConverterNotFound(source, sourceType, targetType);}
以上就是处理标注@RequestParam注解的参数的RequestParamMethodArgumentResolver解析流程。
下面来看一下处理标注@RequestBody注解的参数的RequestResponseBodyMethodProcessor的解析流程,仍然是从resolveArgument方法切入。
// RequestResponseBodyMethodProcessor.java@Overridepublic Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {parameter = parameter.nestedIfOptional();// 在这里完成参数的解析Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());......return adaptArgumentIfNecessary(arg, parameter);}@Overrideprotected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);Assert.state(servletRequest != null, "No HttpServletRequest");ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);// 调用父类AbstractMessageConverterMethodArgumentResolver完成参数解析Object arg = readWithMessageConverters(inputMessage, parameter, paramType);if (arg == null && checkRequired(parameter)) {throw new HttpMessageNotReadableException("Required request body is missing: " +parameter.getExecutable().toGenericString(), inputMessage);}return arg;}
下面进入父类AbstractMessageConverterMethodArgumentResolver的源码。
// AbstractMessageConverterMethodArgumentResolver.java@Nullableprotected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {......EmptyBodyCheckingHttpInputMessage message;try {message = new EmptyBodyCheckingHttpInputMessage(inputMessage);// 遍历HttpMessageConverterfor (HttpMessageConverter<?> converter : this.messageConverters) {Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();GenericHttpMessageConverter<?> genericConverter =(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :(targetClass != null && converter.canRead(targetClass, contentType))) {if (message.hasBody()) {HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);// 实际由MappingJackson2HttpMessageConverter调用父类AbstractJackson2HttpMessageConverter的read方法完成解析,body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);}break;}}}......return body;}// AbstractJackson2HttpMessageConverter.java@Overridepublic Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)throws IOException, HttpMessageNotReadableException {// 获得要转换的目标参数Java类型,如LocalDateTime等JavaType javaType = getJavaType(type, contextClass);// 调用本类的readJavaType方法return readJavaType(javaType, inputMessage);}// AbstractJackson2HttpMessageConverter.javaprivate Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {try {if (inputMessage instanceof MappingJacksonInputMessage) {Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();if (deserializationView != null) {return this.objectMapper.readerWithView(deserializationView).forType(javaType).readValue(inputMessage.getBody());}}// 调用jackson类库,将HTTP的json请求信息解析为需要的参数类型。至此,将json请求转换成目标Java类型return this.objectMapper.readValue(inputMessage.getBody(), javaType);}......}
