springboot时间格式化与空值格式化

目录

    • 1 时间格式化
      • 1.1 出参格式化(Json)
        • 1.1.1 局部配置
        • 1.1.2 全局配置
          • 1.1.2.1 yml配置文件中进行全局配置
          • 1.1.2.2 编写配置类进行全局配置(推荐)
      • 1.2 入参格式化
        • 1.2.1 json参数
          • 1.2.1.1 局部配置
          • 1.2.1.2 全局配置
        • 1.2.2 非json参数
          • 1.2.2.1 局部配置
          • 1.2.2.2 全局配置
    • 2 空值格式化
      • 2.1 响应数据空值格式化
      • 2.2 自定义转换器后时间序列化失效问题
      • 2.3 自定义转换器后时间反序列化失效问题

1 时间格式化

1.1 出参格式化(Json)

创建一个测试类:

@Data
public class TestResponse {

    @ApiModelProperty("未格式化时间")
    private LocalDateTime localDateTime;

    @ApiModelProperty("未格式化时间")
    private LocalDate localDate;

    @ApiModelProperty("未格式化时间")
    private Date date;
}

restApi:

 @GetMapping("test")
 @ApiOperation("响应测试")
 public TestResponse test(){
      TestResponse response = new TestResponse();
      response.setDate(new Date());
      response.setLocalDateTime(LocalDateTime.now());
      response.setLocalDate(LocalDate.now());
      return response;
  }

响应参数带Time的采用的是“ yyyy-MM-dd’T’HH:mm:ss.SSS ”格式,并且DATE默认使用的是UTC时间,比北京时间慢。
springboot时间格式化与空值格式化_第1张图片

1.1.1 局部配置

在字段上加上注解@JsonFormat

	@ApiModelProperty("已格式化时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime localDateTime;


    @ApiModelProperty("已格式化时间")
    @JsonFormat(pattern = "yyyy年MM月dd日")
    private LocalDate localDate;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") //date默认使用UTC时间,北京时间得+8
    @ApiModelProperty("已格式化时间")
    private Date date;

springboot时间格式化与空值格式化_第2张图片

@JsonFormat是jakson自带注解(spring家族默认使用jakson进行序列化与反序列化),常用属性如下:

属性 作用
pattern 格式化模板,如时间的’yyyy-MM-dd HH:mm:ss
shape 决定数据类型,如NUMBER、ARRAY,默认为ANY,任何类型
locale 语言环境
timezone 时区,默认为UTC

springboot时间格式化与空值格式化_第3张图片

在JSR310FormattedSerializerBase类createContextual方法中会检查序列化器,首先会获取目标对象字段上的JsonFormat,如果JsonFormat 不为空,会读取其属性值。根据属性值创建DateTimeFormatter对象,并将其设置为序列化器模板。
最终通过HttpMessageConverter接口实现类的子类MappingJackson2HttpMessageConverter输出响应。MappingJackson2HttpMessageConverter为默认的Json转换类。

springboot时间格式化与空值格式化_第4张图片
也就是更改了LocalDateSerializer、LocalDateTimeSerializer:

springboot时间格式化与空值格式化_第5张图片

springboot时间格式化与空值格式化_第6张图片

1.1.2 全局配置

注释掉@JsomFormat:
springboot时间格式化与空值格式化_第7张图片

1.1.2.1 yml配置文件中进行全局配置
spring:
  jackson:
#    全局配置,但是对LocalDateTime和LocalDate无效
    date-format: yyyy-MM-dd HH:mm:ss
#    时区
    time-zone: GMT+8

重启,可以看到我们在配置文件中设置的格式与时区注入进了ObjectMapper.
springboot时间格式化与空值格式化_第8张图片
因为未对LocalDateSerializer和LocalDateTimeSerializer进行改动,结果只对date生效。
springboot时间格式化与空值格式化_第9张图片

1.1.2.2 编写配置类进行全局配置(推荐)

先取消上一步的配置
springboot时间格式化与空值格式化_第10张图片

手写ObjectMapper配置类:

@Configuration
public class DateTimeConfiguration {

 

    @Bean
    public ObjectMapper initObjectMapper(){ 
        ObjectMapper objectMapper=new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
        objectMapper.registerModule(new JavaTimeModule());
        return objectMapper;
    }

}

运行结果同之前YML中配置一样:
springboot时间格式化与空值格式化_第11张图片
所以LocalDateSerializer和LocalDateTimeSerializer中也得按我们需要的格式进行序列化:

 	@Bean
    public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
        ObjectMapper objectMapper=new ObjectMapper();
        JavaTimeModule javaTimeModule=new JavaTimeModule();
        //针对LocalDateTime和LocalDate
        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        //针对Date类
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
        objectMapper.registerModule(javaTimeModule);
        return objectMapper;
    }

上面localdate字段未被全局序列化覆盖,因为字段上加了注解,说明局部优先于全局:
springboot时间格式化与空值格式化_第12张图片
去掉注解:

springboot时间格式化与空值格式化_第13张图片

springboot时间格式化与空值格式化_第14张图片
也可以通过Jackson2ObjectMapperBuilder来构建新的ObjectMapper实例设置序列化与反序列化方式:

@Configuration
public class DateTimeConfiguration {


 	@Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { //通过ObjectMapper构造器构建实例
        return builder -> {
            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
            //序列化
            builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
            builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
            //反序列化
            builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
            builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
        };
    }

 

   //    @Bean
//    public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
//        ObjectMapper objectMapper=new ObjectMapper();
//        JavaTimeModule javaTimeModule=new JavaTimeModule();
//        //针对LocalDateTime和LocalDate
//        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//        //针对Date类
//        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//        //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
//        objectMapper.registerModule(javaTimeModule);
//        return objectMapper;
//    }

}

1.2 入参格式化

1.2.1 json参数

创建测试接口

	@PostMapping("body")
    @ApiOperation("json请求测试")
    public TestResponse body(@RequestBody TestResponse testResponse){
        log.info("打印的body:{}",testResponse);
        return testResponse;
    }

springboot时间格式化与空值格式化_第15张图片
在这里插入图片描述
因为LocalDateTimeDeserializer与LocalDateDeserializer默认格式模板分别为’yyyy-MM-ddTHH:mm:ss 、'yyyy-MM-dd '.
springboot时间格式化与空值格式化_第16张图片
springboot时间格式化与空值格式化_第17张图片
我们按照默认格式传输自然没问题:
springboot时间格式化与空值格式化_第18张图片
springboot时间格式化与空值格式化_第19张图片

1.2.1.1 局部配置

同样,我们也可以像局部序列化一样局部反序列化时间。
还是使用@JsonFormat注解(序列化与反序列化一致),此时,标了注解字段全局序列化配置失效:

	@ApiModelProperty("已格式化时间")
    @JsonFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
    private LocalDateTime localDateTime;


    @ApiModelProperty("已格式化时间")
    @JsonFormat(pattern = "yyyy年MM月dd日")
    private LocalDate localDate;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") //date默认使用UTC时间,北京时间得+8
    @ApiModelProperty("已格式化时间")
    private Date date;

同序列化时相同,JSR310DateTimeDeserializerBase反序列化时也是读取注解中的属性值,创建新的JsonDeserializer(包括LocalDateTimeDeserializer、LocalDateDeserializer等)
springboot时间格式化与空值格式化_第20张图片

1.2.1.2 全局配置

序列化我们用了全局配置,反序列化自然也应该使用全局配置
去掉注解:
springboot时间格式化与空值格式化_第21张图片
再已有全局配置加上反序列化方式:

	@Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
            //序列化
            builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
            builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
            //反序列化
            builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
            builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
        };
    }

springboot时间格式化与空值格式化_第22张图片
自然也可以自定义并注册ObjectMapper到BEAN

	@Bean
    public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
        ObjectMapper objectMapper=new ObjectMapper();
        JavaTimeModule javaTimeModule=new JavaTimeModule();
        //针对LocalDateTime和LocalDate
        //序列化
        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        //反序列化
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        //针对Date类 序列化和反序列化都是它
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
        objectMapper.registerModule(javaTimeModule);
        return objectMapper;
    }

//    @Bean
//    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
//        return builder -> {
//            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
//            //序列化
//            builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
//            builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//            //反序列化
//            builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
//            builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//        };
//    }

1.2.2 非json参数

将接口改入参改成非JSON类型

 	@PostMapping("param")
    @ApiOperation("普通参数请求测试")
    public TestResponse body(TestResponse testResponse){
        try {
            log.info("打印的param:{}",testResponse);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return testResponse;
    }
    

springboot时间格式化与空值格式化_第23张图片

springboot时间格式化与空值格式化_第24张图片
此时我们配置的json反序列化方式是无效的

1.2.2.1 局部配置

我们再字段上加上注解@DateTimeFormat

	@ApiModelProperty("已格式化时间")
    @DateTimeFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
    private LocalDateTime localDateTime;


    @ApiModelProperty("已格式化时间")
    @DateTimeFormat(pattern = "yyyy年MM月dd日")
    private LocalDate localDate;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") 
    @ApiModelProperty("已格式化时间")
    private Date date;

按对应格式输入参数测试:
springboot时间格式化与空值格式化_第25张图片
非json参数由GenericConversionService类转换,如果由标记了@DateTimeFormat注解,Jsr310DateTimeFormatAnnotationFormatterFactory会返回新一个解析类对象:
springboot时间格式化与空值格式化_第26张图片

FormattingConversionService做为实现类会通过Parser类对象生成GenericConverter并进行转换:
springboot时间格式化与空值格式化_第27张图片

1.2.2.2 全局配置

去掉字段上的注解:
springboot时间格式化与空值格式化_第28张图片
时间配置类中添加如下代码:

 /**
     * string to date
     * @return date
     */
    @Bean
    public DateConverter dateConverter () {
        return new DateConverter();
    }

    /**
     * string to localDate
     * @return localDate
     */
    @Bean
    public LocalDateConverter localDateConverter () {
        return new LocalDateConverter();
    }

    /**
     * string to LocalDateTime
     * @return LocalDateTime
     */
    @Bean
    public LocalDateTimeConverter localDateTimeConverter () {
        return new LocalDateTimeConverter();
    }



    static class DateConverter implements Converter<String, Date> {

        @Override
        public Date convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            String pattern ;
            if (source.length() <= "yyyy-MM-dd".length()) {
                pattern = "yyyy-MM-dd";
            }else {
                pattern = "yyyy-MM-dd HH:mm:ss.SSS";
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
            try {
                return dateFormat.parse(source);
            } catch (ParseException e) {
                throw new RuntimeException(source + " to date error!");
            }
        }
    }

    static class LocalDateConverter implements Converter<String, LocalDate> {

        @Override
        public LocalDate convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
        }
    }

    static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {

        @Override
        public LocalDateTime convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
        }
    }

测试结果:
springboot时间格式化与空值格式化_第29张图片

配置类最终代码:

package com.example.demo.config;


import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.std.NullSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.DurationSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.convert.converter.Converter;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;

@Configuration
public class DateTimeConfiguration {






    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return builder -> {
            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
            //序列化
            builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
            builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
            //反序列化
            builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
            builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
        };
    }



//    @Bean
//    public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
//        ObjectMapper objectMapper=new ObjectMapper();
//        JavaTimeModule javaTimeModule=new JavaTimeModule();
//        //针对LocalDateTime和LocalDate
//        //序列化
//        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//        //反序列化
//        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//        //针对Date类 序列化和反序列化都是它
//        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//        //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
//        objectMapper.registerModule(javaTimeModule);
//        return objectMapper;
//    }


    //以下针对非JSON参数


    /**
     * string to date
     * @return date
     */
    @Bean
    public DateConverter dateConverter () {
        return new DateConverter();
    }

    /**
     * string to localDate
     * @return localDate
     */
    @Bean
    public LocalDateConverter localDateConverter () {
        return new LocalDateConverter();
    }

    /**
     * string to LocalDateTime
     * @return LocalDateTime
     */
    @Bean
    public LocalDateTimeConverter localDateTimeConverter () {
        return new LocalDateTimeConverter();
    }



    static class DateConverter implements Converter<String, Date> {

        @Override
        public Date convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            String pattern ;
            if (source.length() <= "yyyy-MM-dd".length()) {
                pattern = "yyyy-MM-dd";
            }else {
                pattern = "yyyy-MM-dd HH:mm:ss.SSS";
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
            try {
                return dateFormat.parse(source);
            } catch (ParseException e) {
                throw new RuntimeException(source + " to date error!");
            }
        }
    }

    static class LocalDateConverter implements Converter<String, LocalDate> {

        @Override
        public LocalDate convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
        }
    }

    static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {

        @Override
        public LocalDateTime convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
        }
    }
}

2 空值格式化

2.1 响应数据空值格式化

我们返回级前端的各种类型的数据都可能为空,这时候一般我们返回的是NULL,有时候前端会觉得这样不够友好。
如果要转换的话,一般是将字符串转为空串"",集合转为空数组[],数字类转为0,布尔型转为false。本文我们就照着此目标来实现。当然,和前端约定好最重要,怎么方便怎么来,标准毕竟是人定的。

实体类添加些字段:

@Data
public class TestResponse {


    @ApiModelProperty("字符串为NULL")
    private String str;

    @ApiModelProperty("已格式化时间")
//    @DateTimeFormat(pattern = "yyyy年MM月dd日HH时mm分ss秒")
    private LocalDateTime localDateTime;


    @ApiModelProperty("已格式化时间")
//    @DateTimeFormat(pattern = "yyyy年MM月dd日")
    private LocalDate localDate;

//    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
    @ApiModelProperty("已格式化时间")
    private Date date;

    @ApiModelProperty("集合为NULL")
    private List<String> list;
    
    @ApiModelProperty("布尔型为NULL")
    private Boolean bool;

    @ApiModelProperty("数字型为NULL")
    private Integer anInt;

    @ApiModelProperty("LocalDateTime为NULL")
    private LocalDateTime nullLocalDateTime;

    @ApiModelProperty("LocalDate为NULL")
    private LocalDate nullLocalDate;

    @ApiModelProperty("Date为NULL")
    private Date nullDate;

}

依然用之前的测试接口,依然只对几个旧有字段赋值。

	@GetMapping("test")
    @ApiOperation("响应测试")
    public TestResponse test(){
        TestResponse response = new TestResponse();
        response.setDate(new Date());
        response.setLocalDateTime(LocalDateTime.now());
        response.setLocalDate(LocalDate.now());
        return response;
    }


springboot时间格式化与空值格式化_第30张图片
可以加注解或者编写配置类将为null的字段忽略掉,但是这不在本文讨论范畴。
我们可以实现WebMvcConfigurer并重写extendMessageConverters,更改HttpMessageConverter列表,已达到自定义消息转换的目的:

@Configuration
@Slf4j
public class MvcConfig implements WebMvcConfigurer {


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        //fastJson写法
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(
                SerializerFeature.WriteNullStringAsEmpty,                        // 将String类型的字段如果为null,输出为"",而非null

                SerializerFeature.WriteNullNumberAsZero,                         // 将Number类型的字段如果为null,输出为0,而非null

                SerializerFeature.WriteNullListAsEmpty,                          // 将List类型的字段如果为null,输出为[],而非null

                SerializerFeature.WriteNullBooleanAsFalse,                      // 将Boolean类型的字段如果为null,输出为false,而非null

                SerializerFeature.DisableCircularReferenceDetect                // 避免循环引用

        );
        fastConverter.setFastJsonConfig(fastJsonConfig);
        converters.add(0, fastConverter);
    }
}

测试结果:
springboot时间格式化与空值格式化_第31张图片
可以看到除了时间类以外,所有Null字段都转换成了我们自定义的格式。
converters表示HttpMessageConverter列表,其中的元素MappingJackson2HttpMessageConverter做为AbstractJackson2HttpMessageConverter子类是spring中默认的json转换器。我们新建的FastJsonHttpMessageConverter也是AbstractJackson2HttpMessageConverter子类,并且排在默认的MappingJackson2HttpMessageConverter之前,所以替代了MappingJackson2HttpMessageConverter,如果是在MappingJackson2HttpMessageConverter之后是没用的:

springboot时间格式化与空值格式化_第32张图片
重启后:
springboot时间格式化与空值格式化_第33张图片
springboot时间格式化与空值格式化_第34张图片
我们将代码恢复,查看生效时的结果,发现时间类并不是我们定义的格式:
springboot时间格式化与空值格式化_第35张图片
因为我们使用的fastjson,而不是系统默认的Jackson。虽然fastjson效率高但是用在这里没Jackson合适。
这里采用网上看到的写法,自定义MappingJackson2HttpMessageConverter继承类消息转换器,并在内部继承BeanSerializerModifier,重写changeProperties方法(重点):

自定义消息转换器

class JacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {


    /**
     * 处理数组类型的null值
     */
    public class NullArrayJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            if (value == null) {
                jgen.writeStartArray();
                jgen.writeEndArray();
            }
        }
    }


    /**
     * 处理字符串类型的null值
     */
    public class NullStringJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(StringUtils.EMPTY);
        }
    }

    /**
     * 处理数字类型的null值
     */
    public class NullNumberJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeNumber(0);
        }
    }

    /**
     * 处理布尔类型的null值
     */
    public class NullBooleanJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeBoolean(false);
        }
    }



    public class MyBeanSerializerModifier extends BeanSerializerModifier {


        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
            //循环所有的beanPropertyWriter
            for (Object beanProperty : beanProperties) {
                BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
                //判断字段的类型,如果是array,list,set则注册nullSerializer
                if (isArrayType(writer)) {
                    //给writer注册一个自己的nullSerializer
                    writer.assignNullSerializer(new NullArrayJsonSerializer());
                } else if (isNumberType(writer)) {
                    writer.assignNullSerializer(new NullNumberJsonSerializer());
                } else if (isBooleanType(writer)) {
                    writer.assignNullSerializer(new NullBooleanJsonSerializer());
                } else if (isStringType(writer)) {
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                } else if (isDateType(writer)){
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }
                else if (isLcDateTimeType(writer)) {
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }else if (isLcDateType(writer)) {
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }
            }
            return beanProperties;
        }

        /**
         * 是否是数组
         */
        private boolean isArrayType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.isArray() || Collection.class.isAssignableFrom(clazz);
        }

        /**
         * 是否是string
         */
        private boolean isStringType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return CharSequence.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz);
        }


        /**
         * 是否是int
         */
        private boolean isNumberType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return Number.class.isAssignableFrom(clazz);
        }

        /**
         * 是否是boolean
         */
        private boolean isBooleanType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(Boolean.class);
        }


        /**
         * 是否是LocalDate
         */
        private boolean isDateType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(Date.class);
        }


        /**
         * 是否是LocalDate
         */
        private boolean isLcDateType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(LocalDate.class);
        }

        /**
         * 是否是LocalDateTime
         */
        private boolean isLcDateTimeType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(LocalDateTime.class);
        }

    }

    //自定义转换会让全局LocalDateTime序列化失效,默认转Long型
    JacksonHttpMessageConverter() {
        getObjectMapper()
                .setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))//只对Date生效
                .setSerializerFactory(getObjectMapper().getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
    }

}

将自定义转换器添加到转换器列表首位:

 @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    	converters.add(0,new JacksonHttpMessageConverter());
    }

时间格式仍然不符合预期:
springboot时间格式化与空值格式化_第36张图片

2.2 自定义转换器后时间序列化失效问题

在自定义转换器中添加如下代码:

/**
     * 处理LocalDateTime类型的值
     */
    public class LocalDateTimeSerializer extends JsonSerializer<Object> {

        private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(formatter.format((LocalDateTime) o));
        }
    }

    /**
     * 处理LocalDateTime类型的值
     */
    public class LocalDateSerializer extends JsonSerializer<Object> {

        private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy年MM月dd日");

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(formatter.format((LocalDate) o));
        }
    }

修改内部类MyBeanSerializerModifier的changeProperties方法,主要是添加上面定义的序列化方法:

@Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
            //循环所有的beanPropertyWriter
            for (Object beanProperty : beanProperties) {
                BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
                //判断字段的类型,如果是array,list,set则注册nullSerializer
                if (isArrayType(writer)) {
                    //给writer注册一个自己的nullSerializer
                    writer.assignNullSerializer(new NullArrayJsonSerializer());
                } else if (isNumberType(writer)) {
                    writer.assignNullSerializer(new NullNumberJsonSerializer());
                } else if (isBooleanType(writer)) {
                    writer.assignNullSerializer(new NullBooleanJsonSerializer());
                } else if (isStringType(writer)) {
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                } else if (isDateType(writer)){
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }
                else if (isLcDateTimeType(writer)) {
                    //非空使用自定义序列化方式
                    writer.assignSerializer(new LocalDateTimeSerializer());
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }else if (isLcDateType(writer)) {
                    //非空使用自定义序列化方式
                    writer.assignSerializer(new LocalDateSerializer());
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }
            }
            return beanProperties;
        }

结果符合:
springboot时间格式化与空值格式化_第37张图片
大功告成了是吧?没那么简单,自定义转换器后时间序列化与反序列化配置都会失效(使用默认序列化格式),而我们刚才只配置了序列,并没有配置反序列化。

看下我们之前配置的反序列化方式(JSON类型):
springboot时间格式化与空值格式化_第38张图片
调整参数测试接口为json类型:
springboot时间格式化与空值格式化_第39张图片
转换失败:
在这里插入图片描述
说明json入参也得重新序列化

2.3 自定义转换器后时间反序列化失效问题

我们之前通过重写BeanSerializerModifier的方法改变序列化格式,现在也通过重写BeanDeserializerModifier的方法改变反序列化格式。

修改代码:

//反序列化修改器
    public class MyBeanDeSerializerModifier extends BeanDeserializerModifier {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
            Class<?> aClass = deserializer.handledType();
            if (aClass.equals(LocalDateTime.class)) {
                deserializer=new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
            }
            if (aClass.equals(LocalDate.class)) {
                deserializer=new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
            }
            return deserializer;
        }
    }

    //自定义转换会让全局LocalDateTime序列化失效,默认转Long型
    JacksonHttpMessageConverter() {

        DeserializerFactory dFactory = BeanDeserializerFactory.instance.withDeserializerModifier(new MyBeanDeSerializerModifier());
        ObjectMapper objectMapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(dFactory));
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))//只对Date生效
                .setSerializerFactory(getObjectMapper().getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
        setObjectMapper(objectMapper);
    }

测试结果:
springboot时间格式化与空值格式化_第40张图片
在这里插入图片描述

最终代码如下:

时间入参格式化配置

@Configuration
public class DateTimeConfiguration {






//    @Bean
//    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { //时间序列化在
//        return builder -> {
//            builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //date格式,序列化和反序列化都是它
//            //序列化
//            builder.serializerByType(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
//            builder.serializerByType(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//            //反序列化
//            builder.deserializerByType(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒")));
//            builder.deserializerByType(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日")));
//        };
//    }



//    @Bean
//    public ObjectMapper initObjectMapper(){ //自定义ObjectMapper配置并注册BEAN
//        ObjectMapper objectMapper=new ObjectMapper();
//        JavaTimeModule javaTimeModule=new JavaTimeModule();
//        //针对LocalDateTime和LocalDate
//        //序列化
//        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//        //反序列化
//        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
//        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//        //针对Date类 序列化和反序列化都是它
//        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//        //JavaTimeModule 是jackson通过jsr310引入的JAVA8格式化类,没有此Module,序列化后的值会比较乱
//        objectMapper.registerModule(javaTimeModule);
//        return objectMapper;
//    }


    //以下针对非JSON参数


    /**
     * string to date
     * @return date
     */
    @Bean
    public DateConverter dateConverter () {
        return new DateConverter();
    }

    /**
     * string to localDate
     * @return localDate
     */
    @Bean
    public LocalDateConverter localDateConverter () {
        return new LocalDateConverter();
    }

    /**
     * string to LocalDateTime
     * @return LocalDateTime
     */
    @Bean
    public LocalDateTimeConverter localDateTimeConverter () {
        return new LocalDateTimeConverter();
    }



    static class DateConverter implements Converter<String, Date> {

        @Override
        public Date convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            String pattern ;
            if (source.length() <= "yyyy-MM-dd".length()) {
                pattern = "yyyy-MM-dd";
            }else {
                pattern = "yyyy-MM-dd HH:mm:ss.SSS";
            }
            SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
            try {
                return dateFormat.parse(source);
            } catch (ParseException e) {
                throw new RuntimeException(source + " to date error!");
            }
        }
    }

    static class LocalDateConverter implements Converter<String, LocalDate> {

        @Override
        public LocalDate convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
        }
    }

    static class LocalDateTimeConverter implements Converter<String, LocalDateTime> {

        @Override
        public LocalDateTime convert(String source) {
            if (StringUtils.isBlank(source)) {
                return null;
            }
            return LocalDateTime.parse(source, DateTimeFormatter.ofPattern("yyyy年MM月dd日HH时mm分ss秒"));
        }
    }

自定义消息转换

class JacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {


    /**
     * 处理数组类型的null值
     */
    public class NullArrayJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            if (value == null) {
                jgen.writeStartArray();
                jgen.writeEndArray();
            }
        }
    }


    /**
     * 处理字符串类型的null值
     */
    public class NullStringJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(StringUtils.EMPTY);
        }
    }

    /**
     * 处理数字类型的null值
     */
    public class NullNumberJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeNumber(0);
        }
    }

    /**
     * 处理布尔类型的null值
     */
    public class NullBooleanJsonSerializer extends JsonSerializer<Object> {

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeBoolean(false);
        }
    }




    /**
     * 处理LocalDateTime类型的值
     */
    public class LocalDateTimeSerializer extends JsonSerializer<Object> {

        private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(formatter.format((LocalDateTime) o));
        }
    }

    /**
     * 处理LocalDateTime类型的值
     */
    public class LocalDateSerializer extends JsonSerializer<Object> {

        private final DateTimeFormatter formatter= DateTimeFormatter.ofPattern("yyyy年MM月dd日");

        @Override
        public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
            jsonGenerator.writeString(formatter.format((LocalDate) o));
        }
    }







    //序列化修改器
    public class MyBeanSerializerModifier extends BeanSerializerModifier {


        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
            //循环所有的beanPropertyWriter
            for (Object beanProperty : beanProperties) {
                BeanPropertyWriter writer = (BeanPropertyWriter) beanProperty;
                //判断字段的类型,如果是array,list,set则注册nullSerializer
                if (isArrayType(writer)) {
                    //给writer注册一个自己的nullSerializer
                    writer.assignNullSerializer(new NullArrayJsonSerializer());
                } else if (isNumberType(writer)) {
                    writer.assignNullSerializer(new NullNumberJsonSerializer());
                } else if (isBooleanType(writer)) {
                    writer.assignNullSerializer(new NullBooleanJsonSerializer());
                } else if (isStringType(writer)) {
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                } else if (isDateType(writer)){
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }
                else if (isLcDateTimeType(writer)) {
                    //非空使用自定义序列化方式
                    writer.assignSerializer(new LocalDateTimeSerializer());
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }else if (isLcDateType(writer)) {
                    //非空使用自定义序列化方式
                    writer.assignSerializer(new LocalDateSerializer());
                    writer.assignNullSerializer(new NullStringJsonSerializer());
                }
            }
            return beanProperties;
        }

        /**
         * 是否是数组
         */
        private boolean isArrayType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.isArray() || Collection.class.isAssignableFrom(clazz);
        }

        /**
         * 是否是string
         */
        private boolean isStringType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return CharSequence.class.isAssignableFrom(clazz) || Character.class.isAssignableFrom(clazz);
        }


        /**
         * 是否是int
         */
        private boolean isNumberType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return Number.class.isAssignableFrom(clazz);
        }

        /**
         * 是否是boolean
         */
        private boolean isBooleanType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(Boolean.class);
        }


        /**
         * 是否是LocalDate
         */
        private boolean isDateType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(Date.class);
        }


        /**
         * 是否是LocalDate
         */
        private boolean isLcDateType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(LocalDate.class);
        }

        /**
         * 是否是LocalDateTime
         */
        private boolean isLcDateTimeType(BeanPropertyWriter writer) {
            Class<?> clazz = writer.getType().getRawClass();
            return clazz.equals(LocalDateTime.class);
        }

    }

    //反序列化修改器
    public class MyBeanDeSerializerModifier extends BeanDeserializerModifier {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
            Class<?> aClass = deserializer.handledType();
            if (aClass.equals(LocalDateTime.class)) {
                deserializer=new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
            }
            if (aClass.equals(LocalDate.class)) {
                deserializer=new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
            }
            return deserializer;
        }
    }

    //自定义转换会让全局LocalDateTime序列化失效,默认转Long型
    JacksonHttpMessageConverter() {

        DeserializerFactory dFactory = BeanDeserializerFactory.instance.withDeserializerModifier(new MyBeanDeSerializerModifier());
        ObjectMapper objectMapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(dFactory));
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))//只对Date生效
                .setSerializerFactory(getObjectMapper().getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
        setObjectMapper(objectMapper);

配置消息转换器

@Configuration
public class MvcConfig implements WebMvcConfigurer {


    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(0,new JacksonHttpMessageConverter());
    }
}

你可能感兴趣的:(spring,boot,spring,boot,java,json)