SpringBoot關於時間入參、出參之處理


局部處理

  • 入參

    入參就是在Controller層的方法參數中使用到了Date、LocalDateTime去接收前端傳過來的時間參數,或者你是用對象接收,對象里面有Date、LocalDateTime這樣的屬性的。

  • 出參

    從后端返回到前端的數據中帶Date、LocalDateTime這樣的屬性的

入參局部處理用 @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss")注解,出參用 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 注解,
向下面這樣:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User{
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime localDateTime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate localDate;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "HH:mm:ss", timezone = "GMT+8")
    private LocalTime localTime;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date date;
}

需要注意以下幾點:

  1. @DateTimeFormat是Spring的注解,而@JsonFormat是Jackson的注解,還有timezone = "GMT+8"需要帶上,jackson在序列化時間時是按照國際標准時間GMT進行格式化的,而在國內默認時區使用的是CST時區,兩者相差8小時。
  2. @DateTimeFormat的pattern屬性值指定的日期時間格式並不是將要轉換成的日期格式,這個指定的格式是和傳入的參數對應的,假如注解為:@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss"),則傳入的參數應該是這樣的:2018/08/02 22:05:55,否則會拋出異常。
  3. @DateTimeFormat 時,無論是 Date、LocalDateTime、LocalDate、LocalTime都可以用yyyy-MM-dd HH:mm:ss,但是用 @JsonFormat 時就不行,你可以多給框架數據,它可以不要,但是不能少給。

測試Controller:

@RestController
@RequestMapping("/my_test")
public class TestController {

    @GetMapping("/users")
    public User getUser(User user) {
        // 入參測試
        System.out.println(user);
        // 出參測試
        return User.builder()
                .localDateTime(LocalDateTime.now())
                .localDate(LocalDate.now())
                .localTime(LocalTime.now())
                .date(new Date())
                .build();
    }
}

效果如下:

全局處理

入參

處理Date類型的入參很簡單,這樣配置就行了:

Application.yml

# MVC 入參時間處理,不支持 Java8 時間
spring:  
  mvc:
    date-format: 'yyyy-MM-dd HH:mm:ss'

想要支持 Java8 的 LocalDateTime就可以向下面這樣:

直接上代碼吧,原理就是使用 @InitBinder 和 @ControllerAdvice 注解實現,在每個Controller方法執行之前先執行 initBinder() 方法,將前端傳過來的時間字符串進行轉換后再綁定到參數上面。

想了解 @InitBinder 注解的看這里:https://www.cnblogs.com/lvbinbin2yujie/p/10459303.html

GlobalParamsHand.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

import java.beans.PropertyEditorSupport;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/** * 全局參數處理 * <br/> * <p> * 創建人:LeiGQ <br> * 創建時間:2020-07-21 17:09 <br> * <p> * 修改人: <br> * 修改時間: <br> * 修改備注: <br> * </p> */
@Slf4j
@ControllerAdvice
public class GlobalParamsHand {

    /** * The type Custom local date time editor. * * @author leiguoqing * @date 2020 -07-21 23:24:50 */
    private static class CustomLocalDateTimeEditor extends PropertyEditorSupport {
        @Override
        public void setAsText(String text) throws IllegalArgumentException {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
            LocalDateTime localDateTime = LocalDateTime.parse(text, formatter);
            super.setValue(localDateTime);
        }
    }


    /** * 后端接收 LocalDateTime 類型的參數,此方法會在每個Controller方法執行前執行,從而達到將前端傳來的字符串時間轉換為 LocalDateTime * <br/> * Date 類型轉換已在 Application.yml 中使用 Spring.mvc.date-format: 'yyyy-MM-dd HH:mm:ss'配置 * <br/> * 參考:<a href='https://www.jianshu.com/p/cb108ecbec89'>后端接收java.util.Date類型的參數</a> * * @param binder the binder * @author leiguoqing * @date 2020 -07-21 22:48:27 */
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // 注冊 CustomLocalDateTimeEditor
        binder.registerCustomEditor(LocalDateTime.class, new CustomLocalDateTimeEditor());
    }
}

出參

SpringBoot默認是使用Jackson來序列化的,我們就需要重新配置Jackson,像下面這樣:

我把出參的全局配置配置成LocalDateTime和Date均返回時間戳給前端,前端需要什么格式自己定,同時對科學記數法做了處理,如果你想改為類似 yyyy-MM-dd HH:mm:ss 這樣的格式,可以參考這個工具類里面改:https://blog.csdn.net/qq_34845394/article/details/90072563

先來看看下面這個有問題的配置,具體如下:

↓↓↓↓↓↓↓↓↓有問題的配置↓↓↓↓↓↓↓↓↓↓↓↓↓↓

JacksonConfig.java

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.time.LocalDateTime;
import java.util.TimeZone;

/** * ObjectMapper配置 */
@Configuration
public class JacksonConfig {

    @Bean(name = "objMapper")
    @Primary
    public ObjectMapper getObjMapper() {
        ObjectMapper objectMapper = new ObjectMapper();

        // objectMapper.configure() 方法與 objectMapper.disable(), objectMapper.enable() 作用一樣,
        // 都是進行一些配置,查看源碼得知:都是調用 _serializationConfig.without(f) 方法
        /*禁用一些配置*/
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        /*啟用一些配置*/
        // 使科學計數法完整返回給前端
        objectMapper.enable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
        // 時間項目推薦返回前端時間戳,前端根據需要自己轉換格式
        objectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        /*時間模塊*/
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        /*序列化配置, 針對java8 時間 項目推薦返回前端時間戳,前端根據需要自己轉換格式*/
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());

        /*注冊模塊*/
        objectMapper.registerModule(javaTimeModule)
                .registerModule(new Jdk8Module())
                .registerModule(new ParameterNamesModule());

        // 屬性命名策略
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
        // 時區
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));

        // Date 時間格式(非 java8 時間),也統一用時間戳,注釋掉
// objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return objectMapper;
    }

}

LocalDateTimeSerializer.java

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

/** * LocalDateTime 序列化 為時間戳 */
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {

    @Override
    public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
        jsonGenerator.writeNumber(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
    }
}

配置好Jackson之后,還需要把這個 objMapper 配置進 Springboot 的消息轉換器中,像下面這樣:

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    /** * 使用 JacksonConfig 中的 objMapper,兼容 java8 時間 * * @see JacksonConfig#getObjMapper() */
    private final ObjectMapper objMapper;

    // 構造注入
    public MvcConfig(@Qualifier(value = "objMapper") ObjectMapper objMapper) {
        this.objMapper = objMapper;
    }

    /** * 配置消息轉換器 * <br>創建人: leigq * <br>創建時間: 2018-11-14 16:29 * <br> * * @param converters 轉換器 */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 添加映射 Jackson2 Http消息轉換器
        converters.add(customJackson2HttpMessageConverter());
    }

    /** * 映射 Jackson2 Http消息轉換器 * 參考:https://www.cnblogs.com/anemptycup/p/11313481.html */
    @Bean
    public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        jsonConverter.setObjectMapper(objMapper);
        return jsonConverter;
    }

}

這樣我們返回給前端的屬性中有Date或LocalDateTime的就會轉成時間戳了。

↑↑↑↑↑↑↑↑↑↑↑↑↑↑有問題的配置↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

為什么有問題???原因如下:

當我們手動配置了ObjectMapper對象后,在application.yml中的jackson配置會失效,原因是我們手動注入了ObjectMapper對象,SpringBoot檢測到該對象已經存在,則不會再注入了,所以配置自然不會生效了,具體去看這個類源碼就知道了:JacksonHttpMessageConvertersConfiguration

那有沒有一種可以讓application.yml中的配置生效,我們只做配置增強的方法呢?答案是肯定的。

↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓完美的配置↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

JacksonConfig.java

import com.blog.www.util.JacksonUtils;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.LocalDateTime;
import java.util.TimeZone;


/** * ObjectMapper配置,在使用 ObjectMapper 的地方,方便直接注入 ObjectMapper 進行使用, * 但是推薦統一使用 {@link JacksonUtils} 工具類 * * @author leiguoqing * @date 2020 -07-22 21:03:08 */
@Configuration
public class JacksonConfig {

    /** * jackson 2 ObjectMapper 構建定制器 * <br/> * 該段代碼並未覆蓋SpringBoot自動裝配的ObjectMapper對象,而是加強其配置. 詳情請參考: <a href='https://www.jianshu.com/p/68fce8b23341'>SpringBoot2.x下的ObjectMapper配置原理</a> * * @return the jackson 2 object mapper builder customizer * @author leiguoqing * @date 2020 -07-22 21:23:18 */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer customJackson() {
        return jacksonObjectMapperBuilder -> {
            //若POJO對象的屬性值為null,序列化時不進行顯示,暫時注釋掉,為空也顯示
// jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);

            /*禁用一些配置, 已在 application.yml配置*/
// jacksonObjectMapperBuilder.failOnUnknownProperties(false);

            /* 啟用一些配置 */
            // 使科學計數法完整返回給前端,已在 application.yml配置
// jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);

            // 時間項目推薦返回前端時間戳,前端根據需要自己轉換格式, 已在 application.yml配置
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);


            /* 時間模塊 */
            JavaTimeModule javaTimeModule = new JavaTimeModule();
            /* 序列化配置, 針對java8 時間 項目推薦返回前端時間戳,前端根據需要自己轉換格式*/
            javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());

            /* 注冊模塊 */
            jacksonObjectMapperBuilder.modules(javaTimeModule, new Jdk8Module(), new ParameterNamesModule());

            // 屬性命名策略
            jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);

            // 時區, 已在 application.yml配置
// jacksonObjectMapperBuilder.timeZone(TimeZone.getTimeZone("GMT+8"));

            // 針對於Date類型,文本格式化,Date 時間格式(非 java8 時間),也統一用時間戳,注釋掉
// jacksonObjectMapperBuilder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");




            //-----------------------------------------------華麗的分割線---------------------------------------------------

            /* ↓↓↓↓超級詳細的一些配置↓↓↓↓ */
            //若POJO對象的屬性值為null,序列化時不進行顯示
// jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
// //若POJO對象的屬性值為"",序列化時不進行顯示
// jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
// //DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES相當於配置,JSON串含有未知字段時,反序列化依舊可以成功
// jacksonObjectMapperBuilder.failOnUnknownProperties(false);
// //序列化時的命名策略——駝峰命名法
// jacksonObjectMapperBuilder.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
// //針對於Date類型,文本格式化
// jacksonObjectMapperBuilder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
//
// //針對於JDK新時間類。序列化時帶有T的問題,自定義格式化字符串
// JavaTimeModule javaTimeModule = new JavaTimeModule();
// javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// jacksonObjectMapperBuilder.modules(javaTimeModule);
//
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// //默認關閉,將char[]數組序列化為String類型。若開啟后序列化為JSON數組。
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS);
//
// //默認開啟,若map的value為null,則不對map條目進行序列化。(已廢棄)。
// // 推薦使用:jacksonObjectMapperBuilder.serializationInclusion(JsonInclude.Include.NON_NULL);
// jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.WRITE_NULL_MAP_VALUES);
//
// //默認開啟,將Date類型序列化為數字時間戳(毫秒表示)。關閉后,序列化為文本表現形式(2019-10-23T01:58:58.308+0000)
// //若設置時間格式化。那么均輸出格式化的時間類型。
// jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// //默認關閉,在類上使用@JsonRootName(value="rootNode")注解時是否可以包裹Root元素。
// // (https://blog.csdn.net/blueheart20/article/details/52212221)
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRAP_ROOT_VALUE);
// //默認開啟:如果一個類沒有public的方法或屬性時,會導致序列化失敗。關閉后,會得到一個空JSON串。
// jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//
// //默認關閉,即以文本(ISO-8601)作為Key,開啟后,以時間戳作為Key
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
//
// //默認禁用,禁用情況下,需考慮WRITE_ENUMS_USING_TO_STRING配置。啟用后,ENUM序列化為數字
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_INDEX);
//
// //僅當WRITE_ENUMS_USING_INDEX為禁用時(默認禁用),該配置生效
// //默認關閉,枚舉類型序列化方式,默認情況下使用Enum.name()。開啟后,使用Enum.toString()。注:需重寫Enum的toString方法;
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
//
// //默認開啟,空Collection集合類型輸出空JSON串。關閉后取消顯示。(已過時)
// // 推薦使用serializationInclusion(JsonInclude.Include.NON_EMPTY);
// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
//
// //默認關閉,當集合Collection或數組一個元素時返回:"list":["a"]。開啟后,"list":"a"
// //需要注意,和DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY 配套使用,要么都開啟,要么都關閉。
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
//
// //默認關閉。打開后BigDecimal序列化為文本。(已棄用),推薦使用JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN配置
//// jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN);
// //默認關閉,即使用BigDecimal.toString()序列化。開啟后,使用BigDecimal.toPlainString序列化,不輸出科學計數法的值。
// jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
//
// /**
// * JsonGenerator.Feature的相關參數(JSON生成器)
// */
//
// //默認關閉,即序列化Number類型及子類為{"amount1":1.1}。開啟后,序列化為String類型,即{"amount1":"1.1"}
// jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS);
//
// /******
// * 反序列化
// */
// //默認關閉,當JSON字段為""(EMPTY_STRING)時,解析為普通的POJO對象拋出異常。開啟后,該POJO的屬性值為null。
// jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// //默認關閉
//// jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// //默認關閉,若POJO中不含有JSON中的屬性,則拋出異常。開啟后,不解析該字段,而不會拋出異常。
// jacksonObjectMapperBuilder.featuresToEnable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        };
    }
}

LocalDateTimeSerializer.java 和上面的保持一樣。

application.yml

spring:
  # ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ jackson 配置 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ #
  ## 自定義 WebMvcConfig 之后(添加 @EnableWebMvc 后),原有properties中的jackson配置會失效,所以必須在自定義實現類中再次對 jackson 的配置進行補充,
  ## 詳見:com.blog.www.web.config.MvcConfig 中配置的Jackson消息轉換器,原因是我們手動注入了ObjectMapper對象,SpringBoot檢測到該對象已經存在,則不會再注入了,所以配置自然不會生效了
  ## 配置參考:https://www.cnblogs.com/liaojie970/p/9396334.html
  jackson:
    # 反序列化
    deserialization:
      FAIL_ON_UNKNOWN_PROPERTIES: false
      USE_BIG_DECIMAL_FOR_FLOATS: true
    # 序列化
    serialization:
      # 使科學計數法完整返回給前端
      WRITE_BIGDECIMAL_AS_PLAIN: true
      # 時間項目推薦返回前端時間戳
      WRITE_DATES_AS_TIMESTAMPS: true

    # 日期格式化
    date-format: 'yyyy-MM-dd HH:mm:ss'
    time-zone: 'GMT+8'

像上面那樣配置就不會導致application.yml中的配置失效,我們可以看到,我們在application.yml中進行了簡單的配置,然后在JacksonConfig.java中對java8的時間做了處理(application.yml貌似不能配置模塊,所以只能用JavaConfig代碼配置了)。

需要注意的是,如果發現這樣配置不生效,那么很有可能是你在哪個位置加了個 @EnableWebMvc 注解,去掉該注解即可。

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑完美的配置↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

參考資料

非常感謝下面這個博主的文章,收益很大,要想變強,還是得學着看源碼,這樣就不會被被人寫錯的東西坑了:)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM