项目中使用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,自定义参数转换器,如下:
@Configuration
public class DateConverterConfig {
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
};
}
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public 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添加进来,添加的时候需要获取泛型信息:
@Override
public 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";
@Bean
public Converter<String, Date> dateConverter() {
return new Converter<String, Date>() {
@SuppressWarnings("NullableProblems")
@Override
public 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);
}
}
};
}
或者
@Bean
public Converter<String, Date> dateConverter() {
return new Converter<String, Date>() {
/*
* 可对value进行正则匹配,支持日期、时间等多种类型转换
* 这里我偷个懒,在匹配Date日期格式时直接使用了 hutool 为我们已经写好的解析工具类,这里就不重复造轮子了
* cn.hutool.core.date.DateUtil
* @param value
* @return
*/
@SuppressWarnings("NullableProblems")
@Override
public 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
@ControllerAdvice
public class GlobalExceptionHandler {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN)));
}
});
binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN)));
}
});
binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN)));
}
});
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public 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:ss
time-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
*/
@Bean
public 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来实现。
@Configuration
public 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";
@Bean
public 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> {
@Override
public 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> {
@Override
public 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;
日期时间格式化处理方式完整配置
方式一
@Configuration
public 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 的日期解析工具类
*/
@Bean
public Converter<String, Date> dateConverter() {
return new Converter<String, Date>() {
@Override
public Date convert(String source) {
return DateUtil.parse(source.trim());
}
};
}
/**
* Json序列化和反序列化转换器,用于转换Post请求体中的json以及将我们的对象序列化为返回响应的json
*/
@Bean
public 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<>() {
@Override
public 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<>() {
@Override
public 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;
@Configuration
public 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参数
*/
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@SuppressWarnings("NullableProblems")
@Override
public LocalDate convert(String source) {
if (StrUtil.isEmpty(source)) {
return null;
}
return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN));
}
};
}
/**
* LocalDateTime转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@SuppressWarnings("NullableProblems")
@Override
public LocalDateTime convert(String source) {
if (StrUtil.isEmpty(source)) {//使用hutool工具cn.hutool.core.util.StrUtil
return null;
}
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN));
}
};
}
/**
* LocalDate转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, LocalTime> localTimeConverter() {
return new Converter<String, LocalTime>() {
@SuppressWarnings("NullableProblems")
@Override
public LocalTime convert(String source) {
if (StrUtil.isEmpty(source)) {
return null;
}
return LocalTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_TIME_PATTERN));
}
};
}
/**
* Date转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, Date> dateConverter() {
return new Converter<String, Date>() {
@SuppressWarnings("NullableProblems")
@Override
public 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
*/
@Bean
public 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
*/
@Configuration
public 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
*/
@Bean
public 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:ss
spring.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, AbstractHandlerMethodAdapter
handleInternal:793, RequestMappingHandlerAdapter
// 反射获取到实际调用方法,准备开始调用
invokeHandlerMethod:879, RequestMappingHandlerAdapter
invokeAndHandle:105, ServletInvocableHandlerMethod
// 关键步骤,从这里开始处理请求参数
invokeForRequest:134, InvocableHandlerMethod
getMethodArgumentValues:167, InvocableHandlerMethod
resolveArgument:121, HandlerMethodArgumentResolverComposite
下面我们从关键的invokeForRequest:134, InvocableHandlerMethod处开始分析,源码如下
// InvocableHandlerMethod.java
@Nullable
public 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
@Nullable
public 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);
}
// 获取匹配当前方法参数的参数解析器
@Nullable
private 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 = 26
0 = {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);
// 解析给定的方法参数并返回
@Nullable
Object 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
@Nullable
public 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) {
// 创建 DataBinder
WebDataBinder 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
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
// 调用子类的convertIfNecessary方法,这里的具体实现是TypeConverterSupport
return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
}
// TypeConverterSupport.java
@Override
@Nullable
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable MethodParameter methodParam) throws TypeMismatchException {
// 调用重载的convertIfNecessary方法,通过MethodParameter构造了类型描述符TypeDescriptor
return convertIfNecessary(value, requiredType,
(methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
}
// convertIfNecessary方法
@Nullable
@Override
public <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
@Nullable
public <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;
// 查找到类型转换服务 ConversionService
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
// 关键判断,如果没有PropertyEditor 就使用ConversionService
if (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 below
conversionAttemptEx = ex;
}
}
}
Object convertedValue = newValue;
// 关键判断,如果有PropertyEditor就使用PropertyEditor
if (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
@Nullable
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
......
// 从缓存中找到匹配类型的conveter,以LocalDateTime为例,会找到我们自定义的localDateTimeConverter
GenericConverter 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
@Override
public 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);
}
@Override
protected <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
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
......
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// 遍历HttpMessageConverter
for (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
@Override
public 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.java
private 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);
}
......
}