【自定义序列化器】⭐️通过继承JsonSerializer和实现WebMvcConfigurer类完成自定义序列化

目录

前言

解决方案

具体实现

一、自定义序列化器

二、两种方式指定作用域

[1、注解 @JsonSerialize()](#1、注解 @JsonSerialize())

[2、实现自定义全局配置 WebMvcConfigurer](#2、实现自定义全局配置 WebMvcConfigurer)

[三、拓展 WebMvcConfigurer接口](#三、拓展 WebMvcConfigurer接口)

章末


前言

小伙伴们大家好,上次做了自定义对象属性拷贝,解决了重构中尽量不要修改原有逻辑的问题,将String类型的字段转换成Date或者LocalDateTime类型。

【对象属性拷贝】⭐️按照需要转换的类型反射设置拷贝后对象的属性-CSDN博客

但是转换完成后还需要修改Date类型的值为带上时区的,比如

"2024-02-05 14:46:26" 》》》》"2024-02-05 14:46:26 GMT+08:00"

这种借助序列化器实现,可以在很大程度上减少对原有代码的重构

解决方案

自定义一个针对于LocalDateTime字段或者Date类型的序列化器,有两种实现方式,一是使用注解的方式标注哪些实体类中的时间属性需要序列化,二是全局序列化器,在处理返回结果前统一处理

具体实现

一、自定义序列化器
java 复制代码
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

@Slf4j
public class DiyLocalDatetimeSerializer extends JsonSerializer<LocalDateTime> {

    public static final String DEFAULT_DATE_TIME_FORMAT_WITH_TIME_ZONE = "yyyy-MM-dd HH:mm:ss OOOO";
    
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT_WITH_TIME_ZONE);
    //创建一个DateTimeFormatter实例,使用了DEFAULT_DATE_TIME_FORMAT_WITH_TIME_ZONE常量指定的模式。DateTimeFormatter负责根据指定的模式将ZonedDateTime对象格式化为字符串。

        /**
     * 
     * @param localDateTime
     * @param jsonGenerator
     * @param serializerProvider
     * @throws IOException
     */
    @Override
    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        ZoneId zoneId = ZoneId.systemDefault();
        //获取系统默认的时区

        ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
        //转换为具有默认时区的ZonedDateTime对象

        String format = formatter.format(zonedDateTime);
        //格式化ZonedDateTime对象

        jsonGenerator.writeString(format);
        //将格式化后的字符串写入JSON

    }
}
二、两种方式指定作用域
1、注解 @JsonSerialize()

在指定类的属性上面添加该注解,比如需要将user类的time属性加上时区

java 复制代码
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.hb.demo.config.DiyLocalDatetimeSerializer;
import lombok.Data;

import java.time.LocalDateTime;

@Data
public class User {
    private Integer id;
    private String name;
    private  Integer age;
    private String address;
    private String phone;
    private String sex;
    
    @JsonSerialize(using = DiyLocalDatetimeSerializer.class)
    private LocalDateTime time;
}

apipost调用接口测试下,测试接口就是简单的查询数据库表中的数据,先来看下未加注解的返回值

2、实现自定义全局配置 WebMvcConfigurer

注解实现的方式虽然简单,但是架不住每个接口都要改,对应的每个接口的实体类也要改,通过实现WebMvcConfigurer接口,重写了WebMvcConfigurer 接口中的消息转换方法来处理 HTTP 消息的转换。

java 复制代码
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.hb.demo.config.interceptor.DiyInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {


    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(diyConvert());
    }

    /**
     * 自定义消息转换器
     * @Bean 标记该方法返回一个由Spring管理的Bean对象
     * @return
     */
    @Bean
    public MappingJackson2HttpMessageConverter diyConvert(){
        MappingJackson2HttpMessageConverter mappingJackson2CborHttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        //用于JSON 数据的序列化和反序列化

        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(LocalDateTime.class,new DiyLocalDatetimeSerializer());
        //添加针对 LocalDateTime 类型的自定义序列化器 DiyLocalDatetimeSerializer

        objectMapper.registerModule(simpleModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);
        //设置JSON 数据中包含目标对象中不存在的属性时,直接忽略这些属性而不是中断反序列化过程。

        mappingJackson2CborHttpMessageConverter.setObjectMapper(objectMapper);
        return mappingJackson2CborHttpMessageConverter;
    }
}

测试下,先将之前的@JsonSerialize注解去掉,结果如下,通过这种方式可以实现接口传输数据过程中所有指定的类型自动处理

三、拓展 WebMvcConfigurer接口

该接口定义了多个方法,可以自行注册拦截器、资源处理器、视图解析器以及自定义参数解析器等。看下可以重写哪些方法,常用的比如拦截器,资源处理器. . . 使用的时候可以自定义各种全局处理器

后续

使用全局处理后,如果有不需要处理的字段,可以通过加注解的方式标识不需要处理

具体实现

1.新增实体类SerializerField 用于获取当前序列化类信息
java 复制代码
import io.micrometer.core.instrument.util.StringUtils;
import lombok.Data;
import lombok.experimental.Accessors;

import java.util.Objects;

/**
 * 当前序列化的类信息
 */
@Data
@Accessors(chain = true)
public class SerializerField {
    private String fieldName;
    private Class<?> currentClass;
    public boolean effective() {
        return Objects.nonNull(currentClass) && StringUtils.isNotEmpty(fieldName);
    }

    @Override
    public String toString() {
        if (effective()) {
            return currentClass.getName() + " " + fieldName;
        }
        return "";
    }

}
2.新增注解
java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreTimeZoneHanding {
}
3.修改自定义序列化器代码
java 复制代码
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonStreamContext;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.hb.demo.aop.IgnoreTimeZoneHanding;
import com.hb.demo.enity.SerializerField;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;

@Slf4j
public class DiyLocalDatetimeSerializer extends JsonSerializer<LocalDateTime> {

    public static final String DEFAULT_DATE_TIME_FORMAT_WITH_TIME_ZONE = "yyyy-MM-dd HH:mm:ss OOOO";
    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT_WITH_TIME_ZONE);
    private static final DateTimeFormatter formatterNew = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");


    /**
     *
     * @param localDateTime
     * @param jsonGenerator
     * @param serializerProvider
     * @throws IOException
     */
    @Override
    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        SerializerField serializerField = serializerField(jsonGenerator);

        //检查是否需要忽略时区处理
        if (isIgnore(serializerField)) {
            log.info("ignore timeZone handing:{},{}",serializerField,localDateTime);
            String format = formatterNew.format(localDateTime);
            //使用指定的formatterNew格式化localDateTime对象,并将结果写入jsonGenerator

            jsonGenerator.writeString(format);
            return;
        }
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);
        String format = formatter.format(zonedDateTime);
        jsonGenerator.writeString(format);

    }


    //在自定义的序列化器中获取关于当前正在序列化的字段的上下文信息
    public static SerializerField serializerField(JsonGenerator gen) {
        JsonStreamContext outputContext = gen.getOutputContext();
        //从gen(JsonGenerator对象)获取当前的输出上下文

        if (Objects.isNull(outputContext)) {
            return null;
        }
        Object currentValue = outputContext.getCurrentValue();
        //获取当前输出上下文中的当前值

        if (Objects.isNull(currentValue)) {
            return null;
        }

        //构造了一个SerializerField实例,使用当前值的类和当前字段名称进行初始化
        return new SerializerField()
                .setCurrentClass(currentValue.getClass())
                .setFieldName(outputContext.getCurrentName());
    }

    //在自定义的序列化器中判断当前序列化字段是否被标记为忽略时区信息
    public static boolean isIgnore(SerializerField serializerField){
        if(Objects.isNull(serializerField)){
            return false;
        }
        if(!serializerField.effective()){
            return false;
        }
        Field field = ReflectionUtils.findField(serializerField.getCurrentClass(), serializerField.getFieldName());
        if(Objects.isNull(field)){
            return false;
        }

        //判断是否存在IgnoreTimeZoneHanding注解
        IgnoreTimeZoneHanding annotation = field.getAnnotation(IgnoreTimeZoneHanding.class);
        return !Objects.isNull(annotation);

    }

}
4.测试注解生没生效
java 复制代码
    @IgnoreTimeZoneHanding
    private LocalDateTime time;

如图,加了注解的属性不会带上时区属性

章末

相关推荐
程序喵大人43 分钟前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
SomeB1oody1 小时前
【RustyML入门】2.6. 线性判别分析
开发语言·后端·机器学习·rust·教程
程序员雷欧1 小时前
环形缓冲区深度解析:从基础原理到Disruptor源码的全面剖析
java
xiaoqiMikko1 小时前
Dependabot 面板全绿,不代表你的 Tomcat 没洞
java·spring boot
前端开发张小七1 小时前
Java 学习笔记 · 第三课:多线程与并发编程(线程、同步、死锁、Lock、乐观锁与悲观锁)
java·后端·程序员
用户昵称1002 小时前
C/C++编程-工程实践-本地存储log的工程意义
c语言·开发语言
花生了什么事o2 小时前
JVM 垃圾回收:对象如何被判定和回收
java·jvm
evans在进步2 小时前
HashMap 为什么线程不安全?ConcurrentHashMap 如何解决?
java·spring boot·spring
我命由我123452 小时前
匈牙利命名法
java·服务器·后端·学习·java-ee·kotlin·学习方法
闲猫2 小时前
LangChain / Integrations / Integrations by component / Tool
java·数据库·langchain