我是怎麼進行SpringMVC參數校驗的?

前語:

不要爲了讀文章而讀文章,必定要帶着問題來讀文章,勤思考。java

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

甩鍋型

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

勞模型

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

工具型

本身寫個參數校驗的通用工具,而後每一個請求接收到的參數都調用工具方法來校驗,校驗不經過就把校驗結果返回給調用方:bash

半自動型

對 SpringMVC 瞭解比較全面的朋友都知道,它支持 Bean Validation,所以能夠經過使用 javax.validation.constraints 包下的註解,如 @NotNull@Max@Min 等,來實現由框架處理數據校驗。框架

首先,添加 hibernate-validator 依賴(SpringBoot 已經爲咱們自動添加了)。工具

<dependency>
    
<groupId>
org.hibernate.validator
</groupId>
    
<artifactId>
hibernate-validator
</artifactId>
    
<version>
6.0.10.Final
</version>
</dependency>複製代碼

而後,在參數對象的字段上打註解:ui

最後,在 Controller 中給參數對象添加 @Valid 註解,並處理校驗結果:spa

Tip:若是你的參數不是對象,必定要在 Controller 上打 @Validated 註解!hibernate

這樣作,每一個 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作參數校驗時會拋出不一樣的異常,並且這些異常沒有繼承關係,經過異常獲取校驗結果的方式也各不相同(好坑爹~)。

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

對象參數接收請求體,即 RequestBody:

MethodArgumentNotValidException

請求參數綁定到對象參數上:

BindException

普通參數:

ConstraintViolationException
必填參數缺失:
ServletRequestBindingException

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

@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 = t.getBindingResult().getAllErrors().stream()
               .map(
DefaultMessageSourceResolvable
::getDefaultMessage)
               .collect(
Collectors
.joining(
","
));
    } else if (e instanceof 
BindException
) {
        
BindException
 t = (
BindException
) e;
        msg = t.getBindingResult().getAllErrors().stream()
               .map(
DefaultMessageSourceResolvable
::getDefaultMessage)
               .collect(
Collectors
.joining(
","
));
    } 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);
}複製代碼

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

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

那麼問題來了,怎麼才能讓打開頁面的請求返回錯誤頁面,而 ajax 請求返回 json 呢?個人實例代碼已經展現了,有興趣能夠了解一下。


---------------------

相關文章
相關標籤/搜索