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

前語
不要為了讀文章而讀文章,一定要帶著問題來讀文章,勤思考。

image

在 Web 開發(fā)中, 我們經常需要校驗各種參數,這是一件繁瑣又重要的事情,對于很多人來說,在做參數校驗的時候,會有以下幾種類型的處理方式。

甩鍋型

校驗太麻煩了,讓客戶端去負責校驗就行了,調用方傳錯了是調用方的問題,不是服務的問題,甩個 500 錯誤讓他們好好反?。?/p>

image

勞模型

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

image

工具型

自己寫個參數校驗的通用工具,然后每個請求接收到的參數都調用工具方法來校驗,校驗不通過就把校驗結果返回給調用方:。

image

半自動型

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

首先,添加 hibernate-validator 依賴(SpringBoot 已經為我們自動添加了)。

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);"><dependency>

<groupId>
org.hibernate.validator
</groupId>

<artifactId>
hibernate-validator
</artifactId>

<version>
6.0.10.Final
</version>
</dependency>
</pre>

然后,在參數對象的字段上打注解:

image

最后,在 Controller 中給參數對象添加 @Valid 注解,并處理校驗結果:

Tip:如果你的參數不是對象,一定要在 Controller 上打 @Validated 注解!

[圖片上傳中...(image-657a49-1562073850576-1)]

這樣做,每個 Controller 方法都要處理結果,也是很麻煩。

方案分析

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

首先,參數校驗是一件非常重要的事,客戶端要把住第一道防線,而服務方要采取不信任的態(tài)度,做好參數校驗。否則非法請求參數小則影響用戶體驗或者產生垃圾數據,大則會拖跨整個系統(tǒng)!

其次,手工對所有的參數進行校驗相當繁瑣,容易出錯,而且 So boring~

最后,通過工具來完成是比較好的方式,但是必須讓工具變得優(yōu)雅一些。

那么,有沒有更好的解決方案呢?答案是:有的!

最佳實踐

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

想想,如果上面的半自動型例子中,我們不在 Controller 方法中處理校驗結果,會怎么樣呢?答案是,會拋出異常:

image

那么,如果我們做了全局統(tǒng)一異常處理,不就可以實現自動校驗并返回我們想要的結果了嗎?所以我們可以這樣做:

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">@ControllerAdvice
public class 
GlobalExceptionHandler
 {

/** 統(tǒng)一處理參數校驗異常 */

@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);
 }
}
</pre>

然而,如果你只處理 BindException 這個異常的話,你會發(fā)現這個方案有時候好用,有時候卻會“失靈”。為什么呢?因為對于不同的參數解析方式,Spring做參數校驗時會拋出不同的異常,而且這些異常沒有繼承關系,通過異常獲取校驗結果的方式也各不相同(好坑爹~)。

總結起來有以下幾種異常需要處理:

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

MethodArgumentNotValidException

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

BindException

普通參數:

ConstraintViolationException

必填參數缺失:

ServletRequestBindingException

所以完整的處理方法應該是這樣:

<pre style="-webkit-tap-highlight-color: transparent; box-sizing: border-box; font-family: Consolas, Menlo, Courier, monospace; font-size: 16px; white-space: pre-wrap; position: relative; line-height: 1.5; color: rgb(153, 153, 153); margin: 1em 0px; padding: 12px 10px; background: rgb(244, 245, 246); border: 1px solid rgb(232, 232, 232);">@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);
}
</pre>

添加了這個全局異常處理器之后,就可以自動參數校驗了!體驗飛升的感覺~~

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

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

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,632評論 1 32
  • 1、Spring MVC請求流程 (1)初始化:(對DispatcherServlet和ContextLoderL...
    拾壹北閱讀 2,016評論 0 12
  • hibernate validator 校驗操作小結 在controller 的方法定義中加上 @Validate...
    一秒一心跳閱讀 3,119評論 0 1
  • 國家電網公司企業(yè)標準(Q/GDW)- 面向對象的用電信息數據交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,353評論 6 13
  • 社區(qū)里大大小小的超市不下四、五家,有時還是會走順腳去最小、最不起眼的那家。 店里的大爺,個子不高、瘦瘦的,穿著總是...
    立雪寒梅1閱讀 418評論 2 2

友情鏈接更多精彩內容