项目中使用LocalDateTime系列作为DTO中时间的数据类型,但是SpringMVC收到参数后总报错,为了配置全局时间类型转换,尝试了如下处理方式。
注:本文基于Springboot2.x测试,如果无法生效可能是spring版本较低导致的。
PS:如果你的Controller中的LocalDate类型的参数啥注解(RequestParam、PathVariable等)都没加,也是会出错的,因为默认情况下,解析这种参数是使用ModelAttributeMethodProcessor
进行处理,而这个处理器要通过反射实例化一个对象出来,然后再对对象中的各个参数进行convert,但是LocalDate类没有构造函数,无法反射实例化因此会报错!!!
场景
需要完成以下需求:
- 请求参数为格式化 String 类型,需要将其转为时间类型
- 返回数据为时间类型,将其转为指定格式字符串
- 支持 JDK 1.8 时间 API,如:LocalTime、LocalDate、LocalDateTime
GET请求及POST表单日期时间字符串格式转换
这种情况要和时间作为Json字符串时区别对待,因为前端json转后端pojo底层使用的是Json序列化Jackson工具(
HttpMessgeConverter
);而时间字符串作为普通请求参数传入时,转换用的是Converter
,两者在处理方式上是有区别。
解决方案
一、自定义 Converter 转换器
实现 SpringBoot 的 Converter 接口:org.springframework.core.convert.converter.Converter
实际代码:
@SpringBootConfiguration
public class DateTimeConverterConfiguration {
@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"));
}
};
}
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-ddThh:mm"));
}
};
}
}
将这个类标注为配置类,并且添加方法 @Bean,SpringBoot 会注册到容器中,需要的时候会自动加载转换。
当 SpringBoot 会按需求,将需要由 String 转时间类型的自动转换。
注意:
这里不能使用 lambda 表达式,会抛出这个异常:
java.lang.IllegalArgumentException: Unable to determine source type <S> and target type <T> for your Converter [com.huasio.learning.demo.config.DateTimeConverterConfiguration$$Lambda$386/1424788681]; does the class parameterize those types?
无法确定 Converter 类型。
解决办法:
等requestMappingHandlerAdapter
bean注册完成之后再添加自己的converter
就不会注册到FormattingConversionService
中
@Bean
@ConditionalOnBean(name = "requestMappingHandlerAdapter")
public Converter<String, LocalDateTime> localDateTimeConverter() {
return source -> LocalDateTime.parse(source, DateTimeUtils.DEFAULT_FORMATTER);
}
正则匹配
若是使用 Date 类型的话,可以使用一个工具类,识别常用的时间格式。如yyyy-MM-dd HH:mm:ss、yyyy-MM-dd、 HH:mm:ss等,进行匹配。以适应多种场景:
@Component
public class DateConverter implements Converter<String, Date> {
@Override
public Date convert(String value) {
/**
* 可对value进行正则匹配,支持日期、时间等多种类型转换
* @param value
* @return
*/
return DateUtil.parse(value.trim());
}
}
**注:**在匹配Date日期格式时直接使用了 hutool 已经写好的解析工具类,下面的方法同样使用了该工具类,想要在项目中使用该工具类也很简单,在项目pom文件中引入hutool的依赖就可以了,如下:
<!--hu tool 工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.3</version>
</dependency>
二、使用 Spring 注解
Spring 自带的 @DateTimeFormat(pattern="yyyy-MM-dd") 注解,将其标注在字段即可。
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
若是使用自定义 Converter 转换器,则优选使用这个方式,注解不生效。
三、使用 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 {
System.out.println(text);
setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
});
binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern("HH:mm:ss")));
}
});
binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
});
}
}
JSON入参及返回值全局处理
一、修改 application.yml 文件
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;
- 不支持java8日期api;
二、Jackson 序列化和反序列化
@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;
}
}
- 支持(content-type=application/json)请求中格式为
yyyy-MM-dd HH:mm:ss
的字符串,后台用@RequestBody
接收,及返回值Date转为yyyy-MM-dd HH:mm:ss
格式String; - 支持java8日期api;
- 不支持(content-type=application/json)请求中
yyyy-MM-dd
等类型的字符串转为Date;
以上两种方式为JSON入参的全局化处理,推荐使用方式二,尤其适合大型项目在基础包中全局设置。
JSON入参及返回值局部差异化处理
场景: 假如全局日期时间处理格式为:yyyy-MM-dd HH:mm:ss
,但是某个字段要求接收或返回日期yyyy-MM-dd
。
方式一
使用springboot自带的注解@JsonFormat(pattern = "yyyy-MM-dd")
,如下所示:
@JsonFormat(pattern = "yyyy-MM-dd", timezone="GMT+8")
private Date releaseDate;
点评: springboot默认提供,功能强大,满足常见场景使用,并可指定时区。
方式二
自定义日期序列化与反序列化,如下所示:
/**
* 日期序列化
*/
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序列化和反序列化
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;
}
}
相关信息
本文来自:https://www.cnblogs.com/christopherchan/p/12404804.html
经过抽取重点内容形成。感谢这位大佬的文章,在这里作为备份,以防无法访问。
Q.E.D.
Comments | 0 条评论