SpringBoot時間參數處理完整解決方案

在JavaWeb程序的開發過程當中,接口是先後端對接的主要窗口,而接口參數的接收有時候是一個使人頭疼的事情,這其中最困擾程序猿的,應該是時間參數的接收。前端

好比:設置一個用戶的過時時間,前端到底以什麼格式傳遞參數呢?時間戳?仍是2019-12-01 22:13:00這種格式?仍是其餘格式?java

今天我就來總結一下SpringBoot Web應用接口接收時間類型參數的問題解決方案。git

注:目前我對Spring源碼的掌握還不是很好,因此這一篇僅僅總結一下解決方法,後面感悟多了會重寫一下!😎github

示例代碼請前往:https://github.com/laolunsi/spring-boot-examplesweb


通過簡單的測試,咱們知道:spring

  1. 不使用@RequestBody註解的狀況下,全部時間類型參數都會引發報錯;json

  2. 使用@RequestBody,前端傳遞時間戳或2019-11-22形式正常,傳遞2019-11-22 11:22:22報錯,其餘格式一樣報錯。後端

以前有接觸過相似的解決辦法,在類的屬性上加上@DateFormat註解,解決單個時間參數問題。app

可是侷限較多。ide

理想的解決方案是:一次配置,全局通用,多種格式,自動轉換(朗朗上口嗷)

1、源碼簡要分析

首先咱們來簡單分析一下源碼:

image-20191125162144215

深刻的就不解釋了(我如今也不懂🤦‍♂️)

簡單來講,接口接收的參數,首先被HandlerMethodArgumentResolver的實現類處理了一遍,將其轉換爲咱們須要的格式。

這裏主要分爲兩種狀況:

  1. 使用了@RequestBody的參數,通常是對象接收,前端傳遞的一般是JSON形式
  2. 其餘接收參數的方式,好比@RequestAttribute,@RequestParam,或者默認形式,前端傳遞的一般是表單參數、請求URL後綴參數等

2、解決方法

  1. 默認形式,或使用@RequestAttribute,或使用@RequestParam,這樣的參數,經過配置converter來解決問題
  2. 使用@RequestBody解析的參數,經過在ObjectMapper中配置序列化和反序列化規則來處理

2.1 自定義converter

針對第一種狀況,咱們須要配置converter,這裏介紹兩種方法:

  1. @ControllerAdvice + @InitBinder
  2. 直接使用@Bean定義converter類

首先咱們這裏須要一個DateConverter類,這個類實現了Converter接口,重寫了其中的convert方法,將String轉成Date類型:

咱們這裏定義了三種處理格式:

/**
 * 日期轉換類
 * 將標準日期、標準日期時間、時間戳轉換成Date類型
 */
/*@Deprecated*/
public class DateConverter implements Converter<String, Date> {

    private Logger logger = LoggerFactory.getLogger(DateConverter.class);

    private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
    private static final String shortDateFormat = "yyyy-MM-dd";
    private static final String timeStampFormat = "^\\d+$";

    @Override
    public Date convert(String value) {
        logger.info("轉換日期:" + value);

        if(value == null || value.trim().equals("") || value.equalsIgnoreCase("null")) {
            return null;
        }

        value = value.trim();

        try {
            if (value.contains("-")) {
                SimpleDateFormat formatter;
                if (value.contains(":")) {
                    formatter = new SimpleDateFormat(dateFormat);
                } else {
                    formatter = new SimpleDateFormat(shortDateFormat);
                }
                return formatter.parse(value);
            } else if (value.matches(timeStampFormat)) {
                Long lDate = new Long(value);
                return new Date(lDate);
            }
        } catch (Exception e) {
            throw new RuntimeException(String.format("parser %s to Date fail", value));
        }
        throw new RuntimeException(String.format("parser %s to Date fail", value));
    }
}

注:這個DateConverter類在下面都會用到。

import com.aegis.yqmanagecenter.config.date.DateConverter;
import com.aegis.yqmanagecenter.model.bean.common.JsonResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import java.beans.PropertyEditorSupport;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

@ControllerAdvice
public class ControllerHandler {

    private Logger logger = LoggerFactory.getLogger(ControllerHandler.class);

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        // 方法1,註冊converter
        GenericConversionService genericConversionService = (GenericConversionService) binder.getConversionService();
        if (genericConversionService != null) {
            genericConversionService.addConverter(new DateConverter());
        }

        // 方法2,定義單格式的日期轉換,能夠經過替換格式,定義多個dateEditor,代碼不夠簡潔
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        CustomDateEditor dateEditor = new CustomDateEditor(df, true);
        binder.registerCustomEditor(Date.class, dateEditor);


        // 方法3,一樣註冊converter
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(new DateConverter().convert(text));
            }
        });

    }
}

注:上面的三個方法都是利用@ControllerAdvice+@InitBinder來設置時間參數處理的,其中1和3均可以設置DateConverter,而方法2只能一個一個手動設置格式。

這裏須要注意,上述配置方法都沒法解決Json格式數據中的時間參數接收問題。下面咱們直接看完整的解決方案——將DateConverter註冊爲組件,並使用ObjectMapper來配置時間參數的序列化(接口返回值)和反序列化形式(接口接收參數)。

2.2 配置ObjectMapper以及完整解決方案

完整的解決方案:

/**
 * 日期轉換配置
 * 解決@RequestAttribute、@RequestParam和@RequestBody三種類型的時間類型參數接收與轉換問題
 */
@Configuration
public class DateConfig {

    /**
     * 默認日期時間格式
     */
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";

    /**
     * Date轉換器,用於轉換RequestParam和PathVariable參數
     */
    @Bean
    public Converter<String, Date> dateConverter() {
        return new DateConverter();
    }

    /**
     * Json序列化和反序列化轉換器,用於轉換Post請求體中的json以及將咱們的對象序列化爲返回響應的json
     * 使用@RequestBody註解的對象中的Date類型將從這裏被轉換
     */
    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

        JavaTimeModule javaTimeModule = new JavaTimeModule();

        //Date序列化和反序列化
        javaTimeModule.addSerializer(Date.class, new JsonSerializer<Date>() {
            @Override
            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                SimpleDateFormat formatter = new SimpleDateFormat(DEFAULT_DATE_TIME_FORMAT);
                String formattedDate = formatter.format(date);
                jsonGenerator.writeString(formattedDate);
            }
        });
        javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
                return new DateConverter().convert(jsonParser.getText());
            }
        });

        objectMapper.registerModule(javaTimeModule);
        return objectMapper;
    }

}

參考:簡書-Spring中使用LocalDateTime、LocalDate等參數做爲入參

相關文章
相關標籤/搜索