fix bug:解决在Spring项目实践中LocalDateTime无法序列化反序列化的问题
Posted 爱叨叨的程序狗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fix bug:解决在Spring项目实践中LocalDateTime无法序列化反序列化的问题相关的知识,希望对你有一定的参考价值。
概述-本文的意义
JDK 8发行已久,其中不乏一些在实际编码过程中是十分好用的新特性,如JDK 8中的时间特性亦是如此,但是在Spring企业开发中,往往会遇到LocalDateTime
无法序列化/反序列化的问题,原因是LocalDateTime
类型的值在当前的JSON工具中并没有特定的模式去解析该类型。那么解决该问题最简单的方式是使用@JsonFormat
固定一个pattern即可。当时这个注解存在的弊端即为每一个LocalDateTime
类型的参数上都需要一个注解,所以当代码量较大时,工作量就会变大,并且容易因疏忽而出现的Bug,那么使用全局就显得简明很多。
两种方式实现全局配置
两种配置方式
- Jackson配置方式
- FastJson配置方式
这两者均可实现LocalDateTime
类型的序列化/反序列化的目的,使用哪种方式根据读者项目实际情况选择即可。
两种方式的共同原理
最基础的SpringBoot工程中默认集成了Jackson
序列化/反序列化工具,那么在当前版本的Jackson
亦或是FastJson
中默认无法解析LocalDateTime
类型的数据,但是这两种工具均支持自定义序列化/反序列化配置,那么我们自定义一个LocalDateTime
类型的序列化/反序列化方式,并将其注册为Spring中的一个组件即可。
转换工具
LocalDateTimeGetConverter.java
@Configuration
public class LocalDateTimeGetConverter {
@Bean
public Converter<String, LocalDateTime> localDateTimeConverter() {
return new Converter<String, LocalDateTime>() {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(source)), ZoneId.systemDefault());
}
};
}
}
实现一:Jackson
JacksonConfig.java
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return builder -> {
builder.locale(Locale.CHINA);
builder.timeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateEnum.COMMON_DATE_TIME.getDateTimeFormatter()));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateEnum.COMMON_MONTH_DAY.getDateTimeFormatter()));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateEnum.COLON_DELIMITED_TIME.getDateTimeFormatter()));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateEnum.COMMON_DATE_TIME.getDateTimeFormatter()));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateEnum.COMMON_MONTH_DAY.getDateTimeFormatter()));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateEnum.COLON_DELIMITED_TIME.getDateTimeFormatter()));
builder.modules(javaTimeModule);
};
}
@Bean
@Primary
@ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
{
ObjectMapper objectMapper = builder.createXmlMapper(false).build();
// 通过该方法对mapper对象进行设置,所有序列化的对象都将按改规则进行系列化
// Include.Include.ALWAYS 默认
// Include.NON_DEFAULT 属性为默认值不序列化
// Include.NON_EMPTY 属性为 空("") 或者为 NULL 都不序列化,则返回的json是没有这个字段的
// Include.NON_NULL 属性为NULL 不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 允许出现特殊字符和转义符
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
// 允许出现单引号
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
SimpleModule simpleModule = new SimpleModule();
/**
* 将Long,BigInteger序列化的时候,转化为String
*/
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
// 将工具类中的 objectMapper 换为 Spring 中的 objectMapper
JacksonUtil.objectMapper = objectMapper;
return objectMapper;
}
}
DateEnum.java
@Getter
@AllArgsConstructor
public enum DateEnum {
/**
* 时间格式
*/
COMMON_DATE_TIME(0, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault())),
COMMON_SHORT_DATE(1, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm").withZone(ZoneId.systemDefault())),
COMMON_MONTH_DAY(2, DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault())),
COMMON_MONTH(3, DateTimeFormatter.ofPattern("yyyy-MM").withZone(ZoneId.systemDefault())),
YEAR(4, DateTimeFormatter.ofPattern("yyyy").withZone(ZoneId.systemDefault())),
COLON_DELIMITED_TIME(5, DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault())),
COLON_DELIMITED_SHORT_TIME(6, DateTimeFormatter.ofPattern("HH:mm").withZone(ZoneId.systemDefault())),
CHINESE_DATE_TIME(7, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒").withZone(ZoneId.systemDefault())),
CHINESE_SHORT_DATE(8, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分").withZone(ZoneId.systemDefault())),
CHINESE_DATE(9, DateTimeFormatter.ofPattern("yyyy年MM月dd日").withZone(ZoneId.systemDefault())),
CHINESE_MONTH(10, DateTimeFormatter.ofPattern("yyyy年MM月").withZone(ZoneId.systemDefault())),
CHINESE_YEAR(11, DateTimeFormatter.ofPattern("yyyy年").withZone(ZoneId.systemDefault())),
CHINESE_TIME(12, DateTimeFormatter.ofPattern("HH时mm分ss秒").withZone(ZoneId.systemDefault())),
CHINESE_SHORT_TIME(13, DateTimeFormatter.ofPattern("HH时mm分").withZone(ZoneId.systemDefault()));
private final Integer code;
private final DateTimeFormatter dateTimeFormatter;
public static DateEnum findByCode(int code){
return Stream.of(values())
.filter(e -> e.getCode().equals(code))
.findAny()
.orElse(null);
}
}
JacksonUtil.java
@Slf4j
public class JacksonUtil {
public static ObjectMapper objectMapper = new ObjectMapper();
/**
* Java对象转JSON字符串
*
* @param object
* @return
*/
public static String toJsonString(Object object) {
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
log.error("The JacksonUtil toJsonString is error : \\n", e);
throw new RuntimeException();
}
}
/**
* Java对象转JSON字符串 - 美化输出
*
* @param object
* @return
*/
public static String toJsonStringWithPretty(Object object) {
try {
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
} catch (JsonProcessingException e) {
log.error("The JacksonUtil toJsonString is error : \\n", e);
throw new RuntimeException();
}
}
/**
* Java对象转byte数组
*
* @param object
* @return
*/
public static byte[] toJsonBytes(Object object) {
try {
return objectMapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) {
log.error("The JacksonUtil toJsonBytes is error : \\n", e);
throw new RuntimeException();
}
}
/**
* JSON字符串转对象
*
* @param json
* @param clazz
* @param <T>
* @return
*/
public static <T> T parseObject(String json, Class<T> clazz) {
try {
return objectMapper.readValue(json, clazz);
} catch (Exception e) {
log.error("The JacksonUtil parseObject is error, json str is {}, class name is {} \\n", json, clazz.getName(), e);
throw new RuntimeException();
}
}
/**
* JSON字符串转List集合
*
* @param json
* @param elementClasses
* @param <T>
* @return
*/
@SafeVarargs
public static <T> List<T> parseList(String json, Class<T>... elementClasses) {
try {
return objectMapper.readValue(json, getCollectionType(objectMapper, List.class, elementClasses));
} catch (Exception e) {
log.error("The JacksonUtil parseList is error, json str is {}, element class name is {} \\n",
json, elementClasses.getClass().getName(), e);
throw new RuntimeException();
}
}
/**
* JSON字符串转Set集合
*
* @param json
* @param elementClasses
* @param <T>
* @return
*/
@SafeVarargs
public static <T> Set<T> parseSet(String json, Class<T>... elementClasses) {
try {
return objectMapper.readValue(json, getCollectionType(objectMapper, Set.class, elementClasses));
} catch (Exception e) {
log.error("The JacksonUtil parseSet is error, json str is {}, element class name is {} \\n",
json, elementClasses.getClass().getName(), e);
throw new RuntimeException();
}
}
/**
* JSON字符串转Collection集合
*
* @param json
* @param elementClasses
* @param <T>
* @return
*/
@SafeVarargs
public static <T> Collection<T> parseCollection(String json, Class<T>... elementClasses) {
try {
return objectMapper.readValue(json, getCollectionType(objectMapper, Collection.class, elementClasses));
} catch (Exception e) {
log.error("The JacksonUtil parseCollection is error, json str is {}, element class name is {} \\n",
json, elementClasses.getClass().getName(), e);
throw new RuntimeException();
}
}
/**
* 获取泛型的Collection Type
*
* @param collectionClass 泛型的Collection
* @param elementClasses 元素类
* @return JavaType Java类型
* @since 1.0
*/
public static JavaType getCollectionType(ObjectMapper mapper, Class<?> collectionClass, Class<?>... elementClasses) {
return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
}
}
测试Jackson方式
Get方式请求
Get请求时传入时间戳,后端以Long类型接入,而后使用上文的自定义的转换工具将Long
类型转换为LocalDateTime
即可。
@ApiOperation(value = "用户测试", notes = "用户测试notes")
@GetMapping("localDateTime")
public ResultMessage localDateTimeGet(@RequestParam(value = "localDateTime") Long localDateTime) {
return ResultMessage.success(dateTime);
}
@ApiOperation(value = "用户测试", notes = "用户测试notes")
@GetMapping("{localDateTime}")
public ResultMessage localDateTimePath(@PathVariable("localDateTime") Long localDateTime) {
return ResultMessage.success(dateTime);
Post方式请求
@Data
public class LocalDateTimeVO implements Serializable {
private static final long serialVersionUID = 1L;
private LocalDateTime localDateTime;
}
@ApiOperation(value = "用户测试", notes = "用户测试notes")
@PostMapping("localDateTime")
public ResultMessage localDateTimePost(@RequestBody LocalDateTimeVO localDateTimeVO) {
log.info(localDateTimeVO.getLocalDateTime().toString());
return ResultMessage.success(localDateTimeVO);
}
Jackson方式完结撒花
本文参考简书和耳朵实现方式:
https://juejin.cn/post/6854573211528249357 从LocalDateTime序列化探讨全局一致性序列化
实现二:FastJson
LocalDateTimeSerializer.java
public class LocalDateTimeSerializer implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
if 周记3——解决fixed属性在ios软键盘弹出后失效的bug
web移动端fixed布局和input等表单的爱恨情仇 - 终极BUG,完美解决
各浏览器 position: fixed 造成的bug 通用解决办法,Safari, iOS