1. 程式人生 > 程式設計 >詳解SpringBoot時間引數處理完整解決方案

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

在JavaWeb程式的開發過程中,介面是前後端對接的主要視窗,而介面引數的接收有時候是一個令人頭疼的事情,這其中最困擾程式猿的,應該是時間引數的接收。

比如:設定一個使用者的過期時間,前端到底以什麼格式傳遞引數呢?時間戳?還是2019-12-01 22:13:00這種格式?還是其他格式?

今天我就來總結一下SpringBoot Web應用介面接收時間型別引數的問題解決方案。

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

示例程式碼請前往:https://github.com/laolunsi/spring-boot-examples

經過簡單的測試,我們知道:

  1. 不使用@RequestBody註解的情況下,所有時間型別引數都會引起報錯;
  2. 使用@RequestBody,前端傳遞時間戳或2019-11-22形式正常,傳遞2019-11-22 11:22:22報錯,其他格式同樣報錯。

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

但是侷限較多。

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

一、原始碼簡要分析

首先我們來簡單分析一下原始碼:

深入的就不解釋了(我現在也不懂🤦‍♂️)

簡單來說,介面接收的引數,首先被HandlerMethodArgumentResolver的實現類處理了一遍,將其轉換為我們需要的格式。

這裡主要分為兩種情況:

  • 使用了@RequestBody的引數,一般是物件接收,前端傳遞的通常是JSON形式
  • 其他接收引數的方式,比如@RequestAttribute,@RequestParam,或者預設形式,前端傳遞的通常是表單引數、請求URL字尾引數等

二、解決方法

  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等引數作為入參

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。