0%

Spring Boot配置 jackson 序列化一系列的参数

Spring Boot配置 jackson 序列化一系列的参数

背景

  1. 开发阶段,前端组同事要求返回数据中对空值做同意的处理,字符串返回字符串,数组返回空数组等。想起之前见过类似的配置操作,所以一番操作之后。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Configuration
    @EnableWebMvc
    public class JinWeiConvertersConfigurer implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //1.需要先定义一个 convert 转换消息的对象;
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();


    //2、添加fastJson 的配置信息,比如:是否要格式化返回的json数据;
    FastJsonConfig fastJsonConfig = new FastJsonConfig();
    // 不忽略对象属性中的null值
    fastJsonConfig.setSerializerFeatures(
    PrettyFormat,
    WriteMapNullValue,
    WriteNullListAsEmpty,
    WriteNullStringAsEmpty,
    WriteNullNumberAsZero,
    WriteDateUseDateFormat);
    //3、在convert中添加配置信息.
    fastConverter.setFastJsonConfig(fastJsonConfig);
    //4、将convert添加到converters当中.
    converters.add(fastConverter);
    }

    }
  2. 可以注意到这个配置主要是通过 FastJsonHttpMessageConverter 来实现的,不过很快就发现了问题,项目配置的Swagger UI无法访问了,所以就有了如下的代码增加:(在配置累中增加了 addResourceHandlers 函数)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    // 注意 :
    // 1. addResourceHandler 参数可以有多个
    // 2. addResourceLocations 参数可以是多个,可以混合使用 file: 和 classpath : 资源路径
    // 3. addResourceLocations 参数中资源路径必须使用 / 结尾,如果没有此结尾则访问不到
    // 映射到文件系统中的静态文件(应用运行时,这些文件无业务逻辑,但可能被替换或者修改)
    //过滤swagger
    registry.addResourceHandler("swagger-ui.html")
    .addResourceLocations("classpath:/META-INF/resources/“);
    registry.addResourceHandler("/webjars/**")
    .addResourceLocations("classpath:/META-INF/resources/webjars/");
    registry.addResourceHandler("/swagger-resources/**")
    .addResourceLocations("classpath:/META-INF/resources/swagger-resources/");
    registry.addResourceHandler("/swagger/**")
    .addResourceLocations("classpath:/META-INF/resources/swagger*");
    registry.addResourceHandler("/v2/api-docs/**")
    .addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");
    }
  3. 好景不长,很快又发现时间等的格式有些对不上,频繁的修改我意识到是不是又哪儿不太对,通过对系统代码和配置的检查,我意识到同事搭建的这套系统默认使用的是Jackson,所以通过FastJson设置的这些空处置防范无法奏效,所以
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package net.jinwei.config.converters;


    import com.alibaba.fastjson.serializer.SerializeConfig;
    import com.alibaba.fastjson.support.config.FastJsonConfig;
    import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.StringHttpMessageConverter;
    import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


    import java.math.BigDecimal;
    import java.nio.charset.Charset;
    import java.util.List;




    /**
    * 功能简要
    * 消息转换配置
    *
    * @author xbcui
    * createTime 2021/12/24 9:52 AM
    */
    @Configuration
    @EnableWebMvc
    public class JinWeiConvertersConfigurer implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    //在json转换之前先进行string转换
    converters.add(new StringHttpMessageConverter());
    //添加json转换
    MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
    jackson2HttpMessageConverter.setObjectMapper(new ConvertersJsonMapper());
    converters.add(jackson2HttpMessageConverter);
    }


    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //过滤swagger
    registry.addResourceHandler("swagger-ui.html")
    .addResourceLocations("classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**")
    .addResourceLocations("classpath:/META-INF/resources/webjars/");
    registry.addResourceHandler("/swagger-resources/**")
    .addResourceLocations("classpath:/META-INF/resources/swagger-resources/");
    registry.addResourceHandler("/swagger/**")
    .addResourceLocations("classpath:/META-INF/resources/swagger*");
    registry.addResourceHandler("/v2/api-docs/**")
    .addResourceLocations("classpath:/META-INF/resources/v2/api-docs/");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    package net.jinwei.config.converters;


    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;


    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.text.SimpleDateFormat;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.TimeZone;


    import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;


    /**
    * 功能简要
    * 转JSON 空值处理
    *
    * @author xbcui
    * createTime 2021/12/29 5:25 PM
    */
    public class ConvertersJsonMapper extends ObjectMapper {
    public ConvertersJsonMapper() {
    super();
    //收到未知属性时不报异常
    this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
    this.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    this.setTimeZone(TimeZone.getTimeZone("GMT+8"));

    //Long类型转为String类型
    SimpleModule simpleModule = new SimpleModule();
    simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
    simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
    this.registerModule(simpleModule);
    //处理null时设置的值
    this.getSerializerProvider().setNullValueSerializer(new JsonSerializer<Object>() {
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    String fieldName = gen.getOutputContext().getCurrentName();
    try {
    //反射获取字段类型
    Field field = gen.getCurrentValue().getClass().getDeclaredField(fieldName);
    if (Objects.equals(field.getType(), String.class)) {
    //字符串型空值""
    gen.writeString("");
    return;
    } else if (Objects.equals(field.getType(), List.class)) {
    //列表型空值返回[]
    gen.writeStartArray();
    gen.writeEndArray();
    return;
    } else if (Objects.equals(field.getType(), Map.class)) {
    //map型空值返回{}
    gen.writeStartObject();
    gen.writeEndObject();
    return;
    } else if(Objects.equals(field.getType(), Long.class)){
    gen.writeNumber(0);
    return;
    }else if (Objects.equals(field.getType(), String[].class)) {
    //列表型空值返回[]
    gen.writeStartArray();
    gen.writeEndArray();
    return;
    }
    } catch (NoSuchFieldException e) {
    }
    //默认返回""
    gen.writeString("");
    }
    });
    }
    }

HttpMessageConverter

经过这次的修改,我决定总结下相关的东西。

HttpMessageConverter是什么

官方文档介绍:

HttpMessageConverter is responsible for converting from the HTTP request message to an object and converting from an object to the HTTP response body

DispatcherServlet默认已经安装了AnnotationMethodHandlerAdapter作为HandlerAdapter组件的实现类,HttpMessageConverter即由AnnotationMethodHandlerAdapter使用,将请求信息转换为对象,或将对象转换为响应信息。

HttpMessageConverter怎么生效

如何使用HttpMessageConverter 将请求信息转化并绑定到处理方法的入参当中呢?
可以通过两种方式实现:

  1. 使用@RequestBody/@ResponseBody对处理方法进行标注
  2. 使用HttpEntity/ResponseEntity作为处理方法的入参或者返回值

处理方法如何知道请求消息的格式?在处理完成后又是根据什么确定响应消息的格式
可以根据请求消息头的”Content-Type“及Accept属性确定。

HttpMessageConverter这个接口,简单说就是HTTP的request和response的转换器,在遇到@RequestBody时候SpringBoot会选择一个合适的HttpMessageConverter实现类来进行转换,内部有很多实现类,也可以自己实现,如果这个实现类能处理这个数据,那么它的canRead()方法会返回true,SpringBoot会调用他的read()方法从请求中读出并转换成实体类,同样canWrite也是。

自定义HttpMessageConverter实现

文档:
Customization of HttpMessageConverter can be achieved in Java config by overriding configureMessageConverters() if you want to replace the default converters created by Spring MVC, or by overriding extendMessageConverters() if you just want to customize them or add additional converters to the default ones.
如果想替换默认的converter,通过重写configureMessageConverters()这个方法。
而如果你想添加一个自定义converter,通过重写extendMessageConverters()这个方法。