Spring 參數校驗最佳實踐(附完整實例)

聲明:本文屬原創文章,始發於公號:程序員自學之道,同步發佈到 sf,轉載請註明出處。java

不夠好的方案

在 Web 開發中, 咱們常常須要校驗各類參數,這是一件繁瑣又重要的事情,對於不少人來講,在作參數校驗的時候,會有如下幾種類型的處理方式:git

甩鍋型

校驗太麻煩了,讓客戶端去負責校驗就好了,調用方傳錯了是調用方的問題,不是服務的問題,甩個 500 錯誤讓他們好好檢討:
500錯誤程序員

勞模型

有多少參數,我就寫多少個 if 語句作判斷,校驗不經過的都寫一句友好的提示,如:
各類if判斷github

工具型

本身寫個參數校驗的通用工具,而後每一個請求接收到的參數都調用工具方法來校驗,校驗不經過就把校驗結果返回給調用方。這樣確實能減小不少冗餘的代碼:
工具類ajax

半自動型

對 SpringMVC 瞭解比較全面的朋友都知道,它支持 Bean Validation,所以能夠經過使用 javax.validation.constraints 包下的註解,如 @NotNull @Max @Min 等,來實現由框架處理數據校驗:
首先,添加 hibernate-validator 依賴(SpringBoot 項目爲咱們自動添加了):spring

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.10.Final</version>
</dependency>

而後,在參數對象的字段上打註解:
打註解
最後,在 Controller 中給參數對象添加 @Valid 註解,並處理校驗結果:json

  • tip:若是你的參數不是對象,必定要在 Controller 上打 @Validate 註解

處理校驗結果

這樣作,每一個Controller方法進來都要處理結果,也都是冗餘的代碼。瀏覽器

方案分析

以上這些處理方式都有不足之處:框架

  • 首先,參數校驗是一件很是重要的事,客戶端要把住第一道防線,而服務方要採起不信任的態度,作好參數校驗。不然非法請求參數小則影響用戶體驗或者產生垃圾數據,大則會拖跨整個系統
  • 其次,手工對全部的參數進行校驗至關繁瑣,容易出錯,並且 So boring~
  • 最後,經過工具來完成實際上是比較好的方式,可是必須讓工具變得優雅一些

那麼,有沒有更好的解決方案呢?答案是:有的工具

最佳實踐

其實,上面的半自動型的解決方式,只要再進一步,就能夠實現全自動了!

想一想,若是上面的半自動型例子中,咱們不在 Controller 方法中處理校驗結果,會怎麼樣呢?答案是,會拋出異常:
錯誤頁
那麼,若是咱們作了全局統一異常處理,不就能夠實現自動校驗並返回咱們想要的結果了嗎?因此咱們能夠這樣作:

@ControllerAdvice
public class GlobalExceptionHandler {
    /** 統一處理參數校驗異常 */
    @ExceptionHandler
    @ResponseBody
    public ResultBean<?> handleValidationException(BindException e) {
        // 獲取
        String msg = e.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(","));
        log.warn("參數校驗不經過, msg: {}", msg);
        return ResultBean.fail(msg);
    }
}

然而,若是你只統一處理 BindException 這個異常的話,你會發現這個方案有時候好用,有時候卻會「失靈」。爲何呢?由於對於不一樣的參數解析方式,Spring作參數校驗時會拋出不一樣的異常,並且這些異常沒有繼承關係,經過異常獲取校驗結果的方式也各不相同(好坑爹~)。

總結起來有如下幾種異常須要處理:

  • 對象參數接收請求體: MethodArgumentNotValidException
  • 請求參數綁定到對象參數上: BindException
  • 普通參數ConstraintViolationException
  • 必填參數沒傳: ServletRequestBindingException

    • 必填請求參數缺失:MissingServletRequestParameterException
    • 路徑參數缺失:MissingPathVariableException

因此完整的處理方法應該是這樣:

@ExceptionHandler({ConstraintViolationException.class,
            MethodArgumentNotValidException.class,
            ServletRequestBindingException.class,
            BindException.class})
@ResponseBody
public ResultBean<?> handleValidationException(Exception e) {
    String msg = "";
    if (e instanceof MethodArgumentNotValidException) {
        MethodArgumentNotValidException t = (MethodArgumentNotValidException) e;
        msg = getBindingResultMsg(t.getBindingResult());
    } else if (e instanceof BindException) {
        BindException t = (BindException) e;
        msg = getBindingResultMsg(t.getBindingResult());
    } else if (e instanceof ConstraintViolationException) {
        ConstraintViolationException t = (ConstraintViolationException) e;
        msg = t.getConstraintViolations().stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(","));
    } else if (e instanceof MissingServletRequestParameterException) {
        MissingServletRequestParameterException t = (MissingServletRequestParameterException) e;
        msg = t.getParameterName() + " 不能爲空";
    } else if (e instanceof MissingPathVariableException) {
        MissingPathVariableException t = (MissingPathVariableException) e;
        msg = t.getVariableName() + " 不能爲空";
    } else {
        msg = "必填參數缺失";
    }
    log.warn("參數校驗不經過,msg: {}", msg);
    return ResultBean.fail(msg);
}

添加了這個全局異常處理器以後,就能夠自動參數校驗了體驗飛昇的感受~~

完整實例已經上傳到 GitHub,請查看:https://github.com/dadiyang/s...

若是我是在瀏覽器上訪問的,如發一個訪問某個頁面,結果參數校驗不經過,這時這個統一異常處理器會返回一個 json 格式的文本。普通用戶看到這樣的文本,估計要懵圈了。

那麼問題來了,怎麼能讓打開頁面的請求返回錯誤頁面,而 ajax 請求返回 json 呢

個人實例代碼已經展現了,有興趣能夠了解一下,敬請期待下一篇的講解。

相關文章
相關標籤/搜索