
前語:
不要為了讀文章而讀文章,一定要帶着問題來讀文章,勤思考。在 Web 開發中, 我們經常需要校驗各種參數,這是一件繁瑣又重要的事情,對于很多人來說,在做參數校驗的時候,會有以下幾種類型的處理方式。
甩鍋型
校驗太麻煩了,讓用戶端去負責校驗就行了,調用方傳錯了是調用方的問題,不是服務的問題,甩個 500 錯誤讓他們好好檢討:
勞模型
有多少參數,我就寫多少個 if 語句做判斷,校驗不通過的都寫一句友好的提示,如:
工具型
自己寫個參數校驗的通用工具,然後每個請求接收到的參數都調用工具方法來校驗,校驗不通過就把校驗結果傳回給調用方:
半自動型
對 SpringMVC 了解比較全面的朋友都知道,它支援 Bean Validation,是以可以通過使用 javax.validation.constraints 包下的注解,如 @[email protected]@Min 等,來實作由架構處理資料校驗。
首先,添加 hibernate-validator 依賴(SpringBoot 已經為我們自動添加了)。
<dependency>
<groupId>
org.hibernate.validator
</groupId>
<artifactId>
hibernate-validator
</artifactId>
<version>
6.0.10.Final
</version>
</dependency>
然後,在參數對象的字段上打注解:
最後,在 Controller 中給參數對象添加 @Valid 注解,并處理校驗結果:
Tip:如果你的參數不是對象,一定要在 Controller 上打 @Validated 注解!
這樣做,每個 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 呢?我的執行個體代碼已經展示了,有興趣可以了解一下。
---------------------
版權聲明:本文為部落客原創文章,轉載請附上博文連結!