一、引言
Jackson作为Springboot的默认JSON库,对于JDK8的日期序列化没有做默认的支持,需要手动加入jsr310的依赖。
同时,优雅的API设计也应该统一处理日期的序列化,否则前端就需要各种判断。
按照阿里Java开发手册中推荐:前后端的时间格式统一为"yyyy-MM-dd HH:mm:ss",统一为 GMT。
本文详细讲述基于Jackson的日期序列化(没有海外业务,默认GMT+8),可通过properties修改。
二、配置
1. 增加POM依赖
<!--JAVA 8 日期格式化 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
2. 编写配置类
DateConverterProperties
,设置默认格式,可按需通过yml覆盖
package com.qbhj.casejacksonserializer.properties;
import java.util.Locale;
import java.util.TimeZone;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 日期序列化配置类
* @author gmlee
* @date 2022/5/16
*/
@Data
@ConfigurationProperties(prefix = "jackson")
public class DateConverterProperties {
/**
* Date/LocalDateTime 格式化
*/
private String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
/**
* localDate 格式化
*/
private String localDateFormat = "yyyy-MM-dd";
/**
* localTime 格式化
*/
private String localTimeFormat = "HH:mm:ss";
/**
* 时区, 默认 GMT+8
*/
private TimeZone timeZone = TimeZone.getTimeZone("GMT+8");
/**
* 地区, 默认 zh_CN
*/
private Locale locale = Locale.CHINA;
}
MyDateConverterConfig
配置类,指定JDK8日期类的序列化和反序列化器
package com.qbhj.casejacksonserializer.config;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers;
import com.fasterxml.jackson.databind.ser.std.DateSerializer;
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 com.qbhj.casejacksonserializer.properties.DateConverterProperties;
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;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
/**
* 基于Jackson的日期格式配置
*
* @author gmlee
* @date 2022/5/16
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(DateConverterProperties.class)
public class MyDateConverterConfig {
@Autowired
private DateConverterProperties props;
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
log.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
log.info("@================= 初始化jackson日期序列化 ==================@");
log.info("@==========================================================@");
log.info("@========== Locale : 【{}】 ============================@", props.getLocale());
log.info("@========== TimeZone : 【{}】 ======================@", props.getTimeZone().getDisplayName());
log.info("@========== Date : 【{}】 =================@", props.getDateTimeFormat());
log.info("@========== LocalDateTime : 【{}】 ========@", props.getDateTimeFormat());
log.info("@========== LocalDate : 【{}】 =====================@", props.getLocalDateFormat());
log.info("@========== LocalTime : 【{}】 =======================@", props.getLocalTimeFormat());
log.info("@==========================================================@");
log.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
final DateTimeFormatter dateTimeFormatter = this.ofPattern(props.getDateTimeFormat());
final DateTimeFormatter localDateFormatter = this.ofPattern(props.getLocalDateFormat());
final DateTimeFormatter localTimeFormatter = this.ofPattern(props.getLocalTimeFormat());
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(props.getDateTimeFormat());
return builder -> {
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
// 地区
.locale(props.getLocale())
// 时区
.timeZone(props.getTimeZone());
// 序列化
builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter))
.serializerByType(LocalDate.class, new LocalDateSerializer(localDateFormatter))
.serializerByType(LocalTime.class, new LocalTimeSerializer(localTimeFormatter))
.serializerByType(Date.class, new DateSerializer(false, simpleDateFormat));
// 反序列化
builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter))
.deserializerByType(LocalDate.class, new LocalDateDeserializer(localDateFormatter))
.deserializerByType(LocalTime.class, new LocalTimeDeserializer(localTimeFormatter))
.deserializerByType(Date.class,
new DateDeserializers.DateDeserializer(DateDeserializers.DateDeserializer.instance,
simpleDateFormat, props.getDateTimeFormat()));
};
}
/**
* 根据字符串 获取 DateTimeFormatter
*
* @param pattern 日期格式字符串
* @return DateTimeFormatter
*/
private DateTimeFormatter ofPattern(String pattern) {
return DateTimeFormatter.ofPattern(pattern);
}
/**
* LocalDate转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, LocalDate> localDateConverter() {
return new Converter<String, LocalDate>() {
@Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DateTimeFormatter.ofPattern(props.getLocalDateFormat()));
}
};
}
/**
* LocalDateTime转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(source, DateTimeFormatter.ofPattern(props.getDateTimeFormat()));
}
};
}
/**
* LocalTime转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, LocalTime> localTimeConverter() {
return new Converter<String, LocalTime>() {
@Override
public LocalTime convert(String source) {
return LocalTime.parse(source, DateTimeFormatter.ofPattern(props.getLocalTimeFormat()));
}
};
}
/**
* Date转换器,用于转换RequestParam和PathVariable参数
*/
@Bean
public Converter<String, Date> dateConverter() {
return new Converter<String, Date>() {
@Override
public Date convert(String source) {
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(props.getDateTimeFormat());
try {
return simpleDateFormat.parse(source);
} catch (ParseException e) {
log.error(">>>> init Converter String to Date error!", e);
throw new RuntimeException(e);
}
}
};
}
}
3. 编写测试接口
DateSerializerTestEntity
日期类型Entity
package com.qbhj.casejacksonserializer.entity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import lombok.Data;
/**
* 日期序列化测试实体类
*
* @author gmlee
* @date 2021/5/16
*/
@Data
public class DateSerializerTestEntity {
private LocalDateTime localDateTime;
private LocalDate localDate;
private LocalTime localTime;
private Date date;
}
DateSerializerController
测试接口
package com.qbhj.casejacksonserializer.controller;
import com.qbhj.casejacksonserializer.entity.DateSerializerTestEntity;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Date;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 日期序列化测试控制器
*
* @author gmlee
* @date 2022/5/16
*/
@RequestMapping("/date")
@RestController
public class DateSerializerController {
/**
* 获取日期-json序列化
*/
@GetMapping("/get")
public DateSerializerTestEntity get() {
DateSerializerTestEntity dateEntity = new DateSerializerTestEntity();
dateEntity.setLocalDateTime(LocalDateTime.now());
dateEntity.setLocalDate(LocalDate.now());
dateEntity.setLocalTime(LocalTime.now());
dateEntity.setDate(new Date());
return dateEntity;
}
/**
* json反序列化
*/
@PostMapping("/set_body")
public DateSerializerTestEntity setBody(@RequestBody DateSerializerTestEntity dateEntity) {
return dateEntity;
}
/**
* 参数解析
*
*/
@GetMapping("/get_params")
public DateSerializerTestEntity getParams(DateSerializerTestEntity dateEntity) {
return dateEntity;
}
/**
* RequestParam 反序列化测试
*/
@PostMapping("/set_params")
public DateSerializerTestEntity setParams(@RequestParam Date date, @RequestParam LocalDateTime localDateTime,
@RequestParam LocalDate localDate, @RequestParam LocalTime localTime) {
DateSerializerTestEntity entity = new DateSerializerTestEntity();
entity.setDate(date);
entity.setLocalDateTime(localDateTime);
entity.setLocalDate(localDate);
entity.setLocalTime(localTime);
return entity;
}
/**
* PathVariable参数解析
*/
@GetMapping("/get/{date}/{localDateTime}/{localDate}/{localTime}")
public DateSerializerTestEntity getPath(@PathVariable Date date, @PathVariable LocalDateTime localDateTime,
@PathVariable LocalDate localDate, @PathVariable LocalTime localTime) {
DateSerializerTestEntity entity = new DateSerializerTestEntity();
entity.setDate(date);
entity.setLocalDateTime(localDateTime);
entity.setLocalDate(localDate);
entity.setLocalTime(localTime);
return entity;
}
}
三、测试
1. 启动日志
2. API测试,日期序列化格式 yyyy-MM-dd HH:mm:ss
2.1 序列化
2.2 params反序列化
2.3 @RequestBody反序列化
2.4 @RequestParam反序列化
2.5 @PathVariable反序列化
四、代码
https://gitee.com/qbhj/java-cases/tree/master/case-jackson-serializer