一. 前期准备
⏹日期正则标记注解
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface DatePattern {
String value();
}
⏹日期格式类
- 使用内部类聚合了
LocalDate
和LocalDateTime
格式的日期
// 日期格式定数
public final class Jsr310DateFormatPart {
public static class LocalDateFormat {
@DatePattern("^\\d{4}$")
public static final String YYYY = "yyyy";
@DatePattern("^\\d{4}\\d{2}$")
public static final String YYYYMM = "yyyyMM";
@DatePattern("^\\d{4}/\\d{2}$")
public static final String YYYYMM_SLASH = "yyyy/MM";
@DatePattern("^\\d{4}\\d{1,2}\\d{1,2}$")
public static final String YYYYMMDD = "yyyyMMdd";
@DatePattern("^\\d{4}/\\d{2}/\\d{2}$")
public static final String YYYYMMDD_SLASH = "yyyy/MM/dd";
@DatePattern("[0-9]+\\u5e74[0-9]+\\u6708[0-9]+\\u65e5$")
public static final String YYYYMMDD_JP = "yyyy年MM月dd日";
}
public static class LocalDateTimeFormat {
@DatePattern("^\\d{4}\\d{1,2}\\d{1,2} \\d{1,2}:\\d{1,2}:\\d{1,2}$")
public static final String YYYYMMDD_HHMMSS = "yyyyMMdd HH:mm:ss";
@DatePattern("^\\d{4}/\\d{2}/\\d{2} \\d{1,2}:\\d{1,2}:\\d{1,2}$")
public static final String YYYYMMDD_HHMMSS_SLASH = "yyyy/MM/dd HH:mm:ss";
}
}
二. 日期格式转换工具类
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
public final class Jsr310DateUtil {
// LocalDate日期格式List
private static final List<String> LocalDateFormatList = new ArrayList<>();
// LocalDateTime日期格式List
private static final List<String> LocalDateTimeFormatList = new ArrayList<>();
// class与所对应格式的Map
private static final Map<Class<?>, Map<String, String>> clsFormatMap = new HashMap<>();
// 使用静态代码块,可以保证只初始化一次
static {
// 类型的class
List<Class<?>> dateFormatClassList = Arrays.asList(
Jsr310DateFormatPart.LocalDateFormat.class
, Jsr310DateFormatPart.LocalDateTimeFormat.class
);
// LocalDate和LocalDateTime格式与正则的映射
final Map<String, String> LocalDatePatternMap = new LinkedHashMap<>();
final Map<String, String> LocalDateTimePatternMap = new LinkedHashMap<>();
for (Class<?> dateFormatClass : dateFormatClassList) {
// 获取所有的属性
Field[] fields = dateFormatClass.getFields();
for (Field field : fields) {
// 获取属性上的注解
DatePattern annotation = field.getAnnotation(DatePattern.class);
if (ObjectUtils.isEmpty(annotation)) {
continue;
}
// 强制让属性可以访问
ReflectionUtils.makeAccessible(field);
// 日期格式化字符串
String dateFormatStr = "";
try {
// 获取当前属性所对应的属性值
dateFormatStr = (String)field.get(dateFormatClass);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
// 根据类型将LocalDate和LocalDateTime的初始值List和Map获取好
if (Jsr310DateFormatPart.LocalDateFormat.class.equals(dateFormatClass)) {
LocalDateFormatList.add(dateFormatStr);
LocalDatePatternMap.put(dateFormatStr, annotation.value());
} else if (Jsr310DateFormatPart.LocalDateTimeFormat.class.equals(dateFormatClass)) {
LocalDateTimeFormatList.add(dateFormatStr);
LocalDateTimePatternMap.put(dateFormatStr, annotation.value());
}
}
}
clsFormatMap.putAll(
new HashMap<>() {
{
put(LocalDate.class, LocalDatePatternMap);
put(LocalDateTime.class, LocalDateTimePatternMap);
}
}
);
}
// 字符串转换LocalDate
public static LocalDate formatDateStrToLocalDateAllFormat(String dateStr) {
return Jsr310DateUtil.formatDateStrToAllJsr310Date(dateStr, LocalDate.class);
}
// 字符串转LocalDateTime
public static LocalDateTime formatDateStrToLocalDateTimeAllFormat(String dateStr) {
return Jsr310DateUtil.formatDateStrToAllJsr310Date(dateStr, LocalDateTime.class);
}
@SuppressWarnings("unchecked")
private static <T> T formatDateStrToAllJsr310Date(String dateStr, Class<T> cls) {
if (ObjectUtils.isEmpty(dateStr)) {
return null;
}
// 根据class的类型获取所对应的map
Map<String, String> formatPatternMap = clsFormatMap.get(cls);
for (Map.Entry<String, String> mapEntry : formatPatternMap.entrySet()) {
if (!dateStr.matches(mapEntry.getValue())) {
continue;
}
if (LocalDate.class.equals(cls)) {
return (T) Jsr310DateUtil.formatDateStrToLocalDate(dateStr, mapEntry.getKey());
} else if (LocalDateTime.class.equals(cls)) {
return (T) Jsr310DateUtil.formatStringToLocalDateTime(dateStr, mapEntry.getKey());
}
}
return null;
}
// 指定的格式转换为LocalDate
public static LocalDate formatDateStrToLocalDate(String dateStr, String format) {
if (ObjectUtils.isEmpty(dateStr) || !LocalDateFormatList.contains(format)) {
return null;
}
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(format));
}
// 指定的格式转换为LocalDate
public static LocalDateTime formatStringToLocalDateTime(String dateStr, String format) {
if (ObjectUtils.isEmpty(dateStr) || !LocalDateTimeFormatList.contains(format)) {
return null;
}
return LocalDateTime.parse(dateStr, DateTimeFormatter.ofPattern(format));
}
}
三. 创建全局Jsr310日期序列化与反序列化转换类
- 我们通过内部类继承
JsonSerializer<T>
和JsonDeserializer<T>
来实现日期的序列与反序列化 - 通过
builderJsr310JavaTimeModule()
方法来创建聚合好的模块
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public final class GlobalJsr310DateConvert {
// 将LocalDate日期序列化为日期字符串
private static class CustomLocalDateSerializer extends JsonSerializer<LocalDate> {
// LocalDate ==> yyyy年MM月dd日
@Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DateTimeFormatter.ofPattern(Jsr310DateFormatPart.LocalDateFormat.YYYYMMDD_JP)));
}
}
// 将日期字符串反序列化为LocalDate日期
private static class CustomLocalDateDeserializer extends JsonDeserializer<LocalDate> {
@Override
public LocalDate deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String dateStr = p.getValueAsString();
return Jsr310DateUtil.formatDateStrToLocalDateAllFormat(dateStr);
}
}
// 将LocalDateTime日期序列化为日期字符串
private static class CustomLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
// LocalDateTime ==> yyyy/MM/dd HH:mm:ss
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DateTimeFormatter.ofPattern(Jsr310DateFormatPart.LocalDateTimeFormat.YYYYMMDD_HHMMSS_SLASH)));
}
}
// 将日期字符串反序列化为LocalDateTime日期
private static class CustomLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String dateStr = p.getValueAsString();
return Jsr310DateUtil.formatDateStrToLocalDateTimeAllFormat(dateStr);
}
}
// 创建日期序列化与反序列化的模块
public static JavaTimeModule builderJsr310JavaTimeModule() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
// LocalDate的序列和反序列化
javaTimeModule.addSerializer(LocalDate.class, new CustomLocalDateSerializer());
javaTimeModule.addDeserializer(LocalDate.class, new CustomLocalDateDeserializer());
// LocalDateTime的序列和反序列化
javaTimeModule.addSerializer(LocalDateTime.class, new CustomLocalDateTimeSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new CustomLocalDateTimeDeserializer());
return javaTimeModule;
}
}
四. 将转换类注册到ObjectMapper中
- 因为
ObjectMapper
中已经有一部分的时间相关的模块被注册了,所以我们需要先通过MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS
来忽略重复模块的注册
import com.fasterxml.jackson.databind.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration
public class CustomConfig {
@Bean
@Primary
@ConditionalOnMissingBean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
// 通过Jackson2ObjectMapperBuilder的构建者模式创建ObjectMapper对象
return builder
// 禁用忽略重复的模块注册
.featuresToDisable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS)
// 使用我们自定义的jsr310时间模块
.modules(GlobalJsr310DateConvert.builderJsr310JavaTimeModule())
// 将忽略重复的模块注册重新生效,避免被其他地方覆盖
.featuresToDisable(MapperFeature.IGNORE_DUPLICATE_MODULE_REGISTRATIONS)
.build();
}
}
五. 效果
⏹后台Form
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class Test15Form {
private String name;
private String hobby;
private String address;
// java8日期
private LocalDateTime loginTime;
// java8日期
private LocalDate birthday;
}
⏹前台JS
const jsonData = {
name: '贾飞天',
hobby: '睡觉',
// 对应后台form的LocalDate格式的数据
birthday: '20210105',
// 对应后台form的LocalDateTime格式的数据
loginTime: '20210915 05:15:10'
};
$.ajax({
url: url,
type: 'POST',
// 对象转换为json字符串
data: JSON.stringify(jsonData),
contentType: "application/json",
success: function (data, status, xhr) {
console.log(data);
}
});
💪反序列化的效果
💪序列化的效果