Java8已经发布很多年了,但是很多人在开发时仍然坚持使用着Date和SimpleDateFormat进行时间操作。SimpleDateFormat不是线程安全的,而Date处理时间很麻烦,所以Java8提供了LocalDateTime、LocalDate和LocalTime等全新的时间操作API。无论是Date还是LocalDate,在开发Spring Boot应用时经常需要在每个实体类的日期字段上加上@DateTimeFormat注解来接收前端传值与日期字段绑定,加上@JsonFormat注解来让返回前端的日期字段格式化成我们想要的时间格式。时间和日期类型在开发中使用的频率是非常高的,如果每个字段都加上这两个注解的话是非常繁琐的,有没有一种全局设置的处理方式呢?今天就来向大家介绍一下。
注:本文基于Springboot2.3.0版本。
根据不同的请求方式需要做不同的配置,下文中分为了JSON方式传参和GET请求及POST表单方式传参两种情况。
JSON方式传参
这种情况指的是类型POST,Content-Type 是application/json 方式的请求。对于这类请求,controller中需要加上@RequestBody注解来标注到我们用来接收请求参数的局部变量上,代码如下:
@SpringBootApplication
@RestController
public class SpringbootDateLearningApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDateLearningApplication.class, args);
}
/**
* 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";
public static class DateEntity {
private LocalDate date;
private LocalDateTime dateTime;
private Date originalDate;
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
public Date getOriginalDate() {
return originalDate;
}
public void setOriginalDate(Date originalDate) {
this.originalDate = originalDate;
}
}
@RequestMapping("/date")
public DateEntity getDate(@RequestBody DateEntity dateEntity) {
return dateEntity;
}
}
复制代码
假设默认的接收和返回值的格式都是yyyy-MM-dd HH:mm:ss,可以有以下几个方案。
配置application.yml 文件
在application.yml文件中配置上如下内容:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
复制代码
小结:
- 支持Content-Type 是application/json的POST请求,请求参数字符串和返回的格式都是yyyy-MM-dd HH:mm:ss如果请求参数是其他格式,如yyyy-MM-dd字符串则报400 Bad Request异常。
- 不支持LocalDate等Java8日期API。
增加Jackson配置
/**
* 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来实现。
@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;
}
复制代码
以上几种方式都可以实现JSON传参时的全局化配置,更推荐后两种代码中增加配置bean的方式,可以同时支持Date和LocalDate。
GET请求及POST表单方式传参
这种方式和上面的JSON方式,在Spring Boot处理的方式是完全不同的。上一种JSON方式传参是在HttpMessgeConverter中通过jackson的ObjectMapper将http请求体转换成我们写在controller中的参数对象的,而这种方式用的是Converter接口(spring-core中定义的用于将源类型(一般是String)转成目标类型的接口),两者是有本质区别的。
自定义参数转换器(Converter)
自定义一个参数转换器,实现上面提到的org.springframework.core.convert.converter.Converter接口,在配置类里配置上以下几个bean,示例如下:
@Bean
public Converter<String, Date> dateConverter() {
return new Converter<>() {
@Override
public Date convert(String source) {
SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATETIME_PATTERN);
try {
return formatter.parse(source);
} catch (Exception e) {
throw new RuntimeException(String.format("Error parsing %s to Date", source));
}
}
};
}
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<>() {
@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATE_PATTERN));
}
};
}
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<>() {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(DEFAULT_DATETIME_PATTERN));
}
};
}
复制代码
同时把controller接口增加一些参数,可以发现在接口里单独用变量接收也是可以正常转换的。
@RequestMapping("/date")
public DateEntity getDate(
LocalDate date,
LocalDateTime dateTime,
Date originalDate,
DateEntity dateEntity) {
System.out.printf("date=%s, da