下下下周,爭(zhēng)取做只水煮魚(yú)~~~
算了吧,買(mǎi)現(xiàn)成的調(diào)料吧~~~

1 場(chǎng)景
JavaWeb后臺(tái)應(yīng)用程序,具體的執(zhí)行方法,收到請(qǐng)求,需要對(duì)請(qǐng)求的數(shù)據(jù)進(jìn)行基礎(chǔ)校驗(yàn),如字符串長(zhǎng)度限制、正則校驗(yàn)、數(shù)字區(qū)間校驗(yàn)等。
推薦在springMVC中對(duì)前臺(tái)的請(qǐng)求參數(shù)進(jìn)行統(tǒng)一校驗(yàn),校驗(yàn)方式建議采用JSR30標(biāo)準(zhǔn)進(jìn)行校驗(yàn)。
1.1 普通校驗(yàn)方式
最簡(jiǎn)單的校驗(yàn)方式是,對(duì)請(qǐng)求的參數(shù)手動(dòng)一個(gè)個(gè)進(jìn)行校驗(yàn),如下代碼:
@GetMapping("saveWithOld")
public JSONObject saveWithOld(User user) {
JSONObject result = new JSONObject();
if (user.getUserCode() == null || user.getUserCode() == "") {
result.put("success", true);
result.put("message", "用戶代碼不可為空");
return result;
}
if (user.getUserName() == null || user.getUserName() == "") {
result.put("success", true);
result.put("message", "用戶名稱不可為空");
return result;
}
// do something ......
result.put("success", true);
return result;
}
這種方式,代碼量非常大,代碼非常不友好。
1.2 springMVC校驗(yàn)方式
springMVC,在執(zhí)行后臺(tái)方法之前,可以對(duì)請(qǐng)求的數(shù)據(jù)通過(guò)注解進(jìn)行校驗(yàn)。此校驗(yàn)方式基于JSR303規(guī)范。
如下代碼所示:
@Data
public class User {
@NotNull(message = "用戶代碼不可為空")
private String userCode;
}
@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
@PostMapping("saveWithRequestParam")
public JSONObject saveWithRequestParam(@NotNull(message = "用戶代碼不可為空") String userCode) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", userCode);
return result;
}
此種方式,可以使用注解,已更簡(jiǎn)單的方式對(duì)請(qǐng)求參數(shù)進(jìn)行校驗(yàn)。
3 版本說(shuō)明
本文中代碼涉及到的相關(guān)版本如下:
3.1 JDK
JDK1.8
3.2 maven依賴
spring-boot-starter-web中已包含了我們需要的依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7 </version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
<scope>provided</scope>
</dependency>
2 名詞關(guān)系說(shuō)明
這里講下springMVC中使用JSR303進(jìn)行參數(shù)校驗(yàn),相關(guān)的名詞含義及名詞之間的關(guān)系說(shuō)明。
2.1 基本說(shuō)明
springMVC基于JSR303規(guī)范進(jìn)行校驗(yàn)。
官網(wǎng)說(shuō)明:https://jcp.org/en/jsr/detail?id=303
規(guī)范的相關(guān)說(shuō)明如下:
JSR是Java Specification Requests的縮寫(xiě),意思是Java 規(guī)范提案 。
JSR-303 是JAVA EE 6 中的一項(xiàng)子規(guī)范,叫做Bean Validation。
Hibernate Validator是 Bean Validation的參考實(shí)現(xiàn)。
Hibernate Validator提供了JSR 303 規(guī)范中所有內(nèi)置 constrain(約束)的實(shí)現(xiàn),除此之外還有一些附加的constraint(約束)。
2.2 詳細(xì)說(shuō)明
關(guān)于springMVC請(qǐng)求參數(shù)校驗(yàn),涉及幾個(gè)對(duì)應(yīng)的名詞,如下是:
| 名詞 | 說(shuō)明 |
|---|---|
| constraint(約束) | 對(duì)參數(shù)的校驗(yàn)約束注解,如@NotNull表示參數(shù)不可以為Null |
| 校驗(yàn)注解 | 為元素加上約束后,有時(shí)候需要在參數(shù)前加上校驗(yàn)注解來(lái)開(kāi)啟驗(yàn)證。相關(guān)注解有 @Valid和@Validated,如沒(méi)有用到注解獨(dú)有的特性(分組、嵌套)等,用哪個(gè)注解都一樣。需注意不是所有的校驗(yàn)都需要開(kāi)啟校驗(yàn),如下不需要加上校驗(yàn)注解: saveWithRequestParam(@NotNull(message = "用戶代碼不可為空") String userCode) 但是需要在 Controller類上加上注解@Valid或@Validated |
| JSR303規(guī)范 | 行業(yè)規(guī)范標(biāo)準(zhǔn),包括校驗(yàn)的constraint(約束,如@NotNull)和開(kāi)啟校驗(yàn)注解@Valid等體現(xiàn):代碼中體現(xiàn)為 注解、接口,無(wú)具體實(shí)現(xiàn)代碼 jar包:jakarta.validation-api-2.0.2.jar 約束注解:javax.validation.constraints包下注解+hibernate增強(qiáng)注解org.hibernate.validator.constraints 校驗(yàn)注解: javax.validation.Valid
|
| Hibernate Validator | Hibernate對(duì)JSR303規(guī)范中的約束constraint的具體代碼實(shí)現(xiàn)jar包:hibernate-validator-6.0.20.Final.jar 增強(qiáng):在原有JSR303的 constraint(約束)中增加了約束(如@Range)
|
| spring JSR303 | spring對(duì)JSR303的包裝,對(duì)原有的校驗(yàn)進(jìn)行了增強(qiáng)增強(qiáng): 分組校驗(yàn)、順序校驗(yàn)缺點(diǎn): 不支持嵌套校驗(yàn)約束注解:javax.validation.constraints包下注解+hibernate增強(qiáng)注解org.hibernate.validator.constraints 校驗(yàn)注解: org.springframework.validation.annotation.Validated增強(qiáng)說(shuō)明:所謂的包裝和增強(qiáng),只是將 @Valid注解擴(kuò)展為@Validated注解。約束注解和JSR303一樣。 |
2.3 關(guān)系圖
一圖勝千言。參數(shù)校驗(yàn)的相關(guān)說(shuō)明,關(guān)系圖如下:

3 校驗(yàn)流程
3.1 對(duì)象參數(shù)
3.1.1 說(shuō)明
在對(duì)象參數(shù)中進(jìn)行約束校驗(yàn)。需滿足以下條件:
(1)在mapping方法中通過(guò)注解@Valid或@Validated指定要校驗(yàn)的參數(shù)對(duì)象
如下:
@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {......}
(2)在對(duì)象參數(shù)對(duì)應(yīng)的類中,對(duì)需要校驗(yàn)的參數(shù)加上約束注解
如下:
@Data
public class User {
/**
* 用戶代碼
*/
@NotNull(message = "用戶代碼不可為空")
private String userCode;
}
3.1.2 校驗(yàn)流程
校驗(yàn)失敗后,需要對(duì)失敗的異常信息進(jìn)行處理,處理方式有兩種:
1、在mapping方法上加上參數(shù)BindingResult bindingResult
此種方式,校驗(yàn)失敗后,會(huì)將異常信息封裝到參數(shù)對(duì)象bindingResult中,可以自行對(duì)其中的異常信息進(jìn)行處理,封裝錯(cuò)位信息,返回請(qǐng)求結(jié)果。
這種情況,需要每個(gè)請(qǐng)求,都對(duì)參數(shù)BindingResult進(jìn)行處理,較為繁瑣,不建議此種方式。
如下:
@GetMapping("saveWithBind")
public JSONObject saveWithBind(@Valid User user, BindingResult bindingResult) {
// --------------------[手動(dòng)檢測(cè)驗(yàn)證是否通過(guò)]--------------------
if (bindingResult.hasErrors()) {
for (FieldError fieldError : bindingResult.getFieldErrors()) {
JSONObject result = new JSONObject();
result.put("success", false);
result.put("message", fieldError.getDefaultMessage());
return result;
}
}
// --------------------[驗(yàn)證檢測(cè)通過(guò)后執(zhí)行其他操作]--------------------
// ......
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
2、定義spring全局異常處理,捕捉對(duì)應(yīng)的異常信息,進(jìn)行統(tǒng)一處理
mapping方法上不加參數(shù)BindingResult bindingResult,校驗(yàn)失敗后,會(huì)拋出異常,異常信息,通過(guò)spring全局異常管理,統(tǒng)一對(duì)拋出的異常信息進(jìn)行處理,處理后統(tǒng)一封裝錯(cuò)位信息。這種方式,代碼量較少,且處理錯(cuò)誤信息集中,推薦此種方式。
如下代碼:
@Data
public class User {
/**
* 用戶代碼
*/
@NotNull(message = "用戶代碼不可為空")
private String userCode;
}
// 參數(shù)校驗(yàn)失敗,拋出異常:org.springframework.validation.BindException
@GetMapping("saveWithNormal")
public JSONObject saveWithNormal(@Valid User user) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
/**
* 捕捉全局異常:org.springframework.validation.BindException
* <div>普通請(qǐng)求的參數(shù),校驗(yàn)失敗,拋出此異常</div>
* <div>如:(@Valid User user)</div>
*
* @param exception
* @return
*/
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
log.info("全局異常[BindException]:" + exception.getMessage());
JSONObject result = new JSONObject();
result.put("success", false);
if (exception != null) {
String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
result.put("message", message);
}
return result;
}
需注意:參數(shù)上@Valid和@Validated使用方式的不同,校驗(yàn)失敗后,會(huì)拋出不同的異常:
如下:
/**
* 捕捉全局異常:org.springframework.validation.BindException
* <div>普通請(qǐng)求的參數(shù),校驗(yàn)失敗,拋出此異常</div>
* <div>如:(@Valid User user)</div>
* @param exception
* @return
*/
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {......}
/**
* 捕捉全局異常:org.springframework.web.bind.MethodArgumentNotValidException
* <div>@RequestBody修飾的參數(shù),校驗(yàn)失敗,拋出此異常</div>
* <div>如:(@RequestBody @Valid User user)</div>
* @param exception
* @return
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public JSONObject handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {......}
總結(jié)校驗(yàn)流程圖如下:

3.2 普通類型參數(shù)
3.2.1 說(shuō)明
在普通類型參數(shù)中進(jìn)行約束校驗(yàn)。需滿足以下條件:
(1)在Controller類上通過(guò)注解@Validated開(kāi)啟校驗(yàn)
注意:需為@Validated注解,而不是@Valid注解
如下:
@Validated
@RestController
@RequestMapping("user")
public class UserController {......}
(2)在mapping方法的普通類型參數(shù)前面加上約束注解
如下:
// 參數(shù)校驗(yàn)失敗,拋出異常:javax.validation.ConstraintViolationException
@PostMapping("saveWithRequestParam")
public JSONObject saveWithRequestParam(@NotNull(message="用戶代碼不可為空") String userCode){......}
// 參數(shù)校驗(yàn)失敗,拋出異常:org.springframework.web.bind.ConstraintViolationException
@GetMapping("saveWithRestful/{userCode}")
public JSONObject saveWithRest(@PathVariable("userCode") @Length(max = 10,message="用戶代碼不可超過(guò)10位") String userCode) {......}
3.2.1 校驗(yàn)流程
校驗(yàn)結(jié)果的處理流程同《3.1對(duì)象參數(shù)》
需注意:如使用全局異常捕捉,校驗(yàn)失敗后拋出的異常如下:
/**
* 捕捉全局異常:javax.validation.ConstraintViolationException
* <div>直接在參數(shù)上加的校驗(yàn),校驗(yàn)失敗,拋出此異常</div>
* <div>如:(@NotNull(message = "用戶代碼不可為空") String userCode)</div>
*
* @param exception
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public JSONObject handlerConstraintViolationException(ConstraintViolationException exception) {......}
總結(jié)校驗(yàn)流程圖如下:

4 嵌套校驗(yàn)
嵌套校驗(yàn),準(zhǔn)確來(lái)說(shuō),是對(duì)象內(nèi)約束的嵌套校驗(yàn)。指的是校驗(yàn)A對(duì)象,A對(duì)象內(nèi)有個(gè)屬性B是對(duì)象,B對(duì)象內(nèi)部屬性仍然有約束。需要對(duì)A對(duì)象的約束+A對(duì)象內(nèi)B對(duì)象的約束進(jìn)行校驗(yàn),這種就是嵌套約束。
4.1 代碼示例
這里既校驗(yàn)參數(shù)user中的userCode約束又需要校驗(yàn)user中的屬性對(duì)象department中的departmentCode的約束。
- 實(shí)體定義
@Data
public class Department {
@NotNull(message = "部門(mén)代碼不可為空")
private String departmentCode;
}
@Data
public class User {
@NotNull(message = "用戶代碼不可為空")
private String userCode;
@Valid
@NotNull(message = "部門(mén)不可為空")
private Department department;
private String userName;
}
- mapping方法
@GetMapping("saveWithLevel")
public JSONObject saveWithLevel(@Valid User user) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
- 異常處理
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
log.info("全局異常[BindException]:" + exception.getMessage());
JSONObject result = new JSONObject();
result.put("success", false);
if (exception != null) {
String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
result.put("message", message);
}
return result;
}
4.2 代碼測(cè)試
- 請(qǐng)求信息
http://localhost:8080/user/saveWithLevel?department.departmentCode=001
- 返回結(jié)果
{"success":false,"message":"部門(mén)代碼長(zhǎng)度需在5~10之間,用戶代碼不可為空"}
可見(jiàn)嵌套校驗(yàn)起作用了,對(duì)象user的內(nèi)部普通屬性u(píng)serCode和內(nèi)部對(duì)象department的自己的約束都起作用了。
4.3 總結(jié)
實(shí)現(xiàn)嵌套校驗(yàn),在被校驗(yàn)對(duì)象的
內(nèi)部屬性對(duì)象上,必須加上@Valid注解mapping方法參數(shù)前,用
@Valid注解和@Validated沒(méi)有區(qū)別
5 分組校驗(yàn)
同一個(gè)javaBean,我們加上約束注解后,這個(gè)javaBean作為請(qǐng)求參數(shù)的對(duì)象類型,其中的約束注解,會(huì)對(duì)參數(shù)對(duì)象的內(nèi)容進(jìn)行校驗(yàn)。
有時(shí)候,不同的請(qǐng)求我們會(huì)使用相同的javaBean作為對(duì)象的參數(shù)類型,如新增用戶和更新用戶我們都會(huì)使用用戶這個(gè)JavaBean作為請(qǐng)求參數(shù)的封裝對(duì)象。
5.1 代碼示例
比如,我們新增用戶,需要設(shè)置密碼;更新用戶,不需要設(shè)置密碼。
代碼如下:
- 分組類型
分組接口不需要有實(shí)現(xiàn),僅僅作為一個(gè)分組類型
public interface Add {
}
public interface Edit {
}
- 實(shí)體定義
通過(guò)約束中的group參數(shù),來(lái)指定對(duì)應(yīng)的分組類型,可以指定多個(gè)
@Data
public class User {
@NotNull(message = "用戶代碼不可為空", groups = {Add.class, Edit.class})
private String userCode;
@NotNull(message = "密碼不可為空", groups = {Add.class})
private String password;
}
- mapping方法
校驗(yàn)方式,只能指定@Validated,其中的value為這個(gè)參數(shù)的分組類型,和類中約束注解的groups屬性相對(duì)性,可以指定多個(gè)。
@GetMapping("groupAdd")
public JSONObject groupAdd(@Validated(Add.class) User user) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
@GetMapping("groupEdit")
public JSONObject groupEdit(@Validated(Edit.class) User user) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
- 異常處理
同4.1
5.2 代碼測(cè)試
-
新增
-
請(qǐng)求
-
結(jié)果
{"success":false,"message":"用戶代碼不可為空,密碼不可為空"}
-
-
編輯
-
請(qǐng)求
-
結(jié)果
{"success":false,"message":"用戶代碼不可為空"}
-
5.3 總結(jié)
(1)分組校驗(yàn)中,定義的分組類型接口,不需要有實(shí)現(xiàn)內(nèi)容,僅僅是作為分組的一個(gè)類型存在,不同的業(yè)務(wù),可以共用相同的類型。
(2)約束中的分組類型,可以定義多個(gè)。
(3)@Validated中的分組類型,也可以指定多個(gè)。
(4)校驗(yàn)的時(shí)候,根據(jù)@Validated中指定分組類型,,去找校驗(yàn)對(duì)象中的對(duì)應(yīng)有此分組類型的約束,進(jìn)行校驗(yàn)。
(5)指定分組后,不滿足分組的約束(不加分組的約束為默認(rèn)分組,也是一種分組),不會(huì)進(jìn)行校驗(yàn)
6 順序校驗(yàn)
如不進(jìn)行順序校驗(yàn)配置,校驗(yàn)對(duì)象內(nèi)的屬性,校驗(yàn)順序是隨機(jī)的。
有時(shí)候想先校驗(yàn)比較簡(jiǎn)單的約束,再校驗(yàn)復(fù)雜的,因此需要指定約束的校驗(yàn)順序。可以結(jié)合《7 驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止》一起使用。
6.1 代碼示例
- 分組類型
// 分組類型:第一個(gè)執(zhí)行
public interface FirstCheck {
}
// 分組類型:第二個(gè)執(zhí)行
public interface SecondCheck {
}
// 待順序的分組類型組
@GroupSequence({FirstCheck.class, SecondCheck.class})
public interface UserGroupCheck {
}
- 實(shí)體定義
@Data
public class User {
@NotNull(message = "用戶代碼不可為空", groups = {FirstCheck.class})
private String userCode;
@NotNull(message = "密碼不可為空", groups = {SecondCheck.class})
private String password;
@NotNull(message = "用戶名不可為空")
private String userName;
}
- mapping方法
@GetMapping("orderCheck")
public JSONObject orderCheck(@Validated(UserGroupCheck.class) User user) {
JSONObject result = new JSONObject();
result.put("success", true);
result.put("message", user.toString());
return result;
}
- 異常處理
同4.1
6.2 代碼測(cè)試
-
請(qǐng)求1
- 請(qǐng)求
http://localhost:8080/user/orderCheck
- 結(jié)果
{"success":false,"message":"用戶代碼不可為空"} -
請(qǐng)求2
- 請(qǐng)求
http://localhost:8080/user/orderCheck?userCode=001
- 結(jié)果
{"success":false,"message":"密碼不可為空"}請(qǐng)求3
-
- 請(qǐng)求
http://localhost:8080/user/orderCheck?userCode=001&password=123456
- 結(jié)果
{"success":true,"message":"User(userCode=001, password=123456, userName=null)"} -
特殊請(qǐng)求
-
變更
將實(shí)體類進(jìn)行變更,userName上的約束也加上分組為FirstCheck。此時(shí)userCode和userName的約束分組均為FirstCheck。
如下:
@Data public class User { @NotNull(message = "用戶代碼不可為空", groups = {FirstCheck.class}) private String userCode; @NotNull(message = "密碼不可為空", groups = {SecondCheck.class}) private String password; @NotNull(message = "用戶名不可為空", groups = {FirstCheck.class}) private String userName; } -
請(qǐng)求
結(jié)果
-
{"success":false,"message":"用戶代碼不可為空,用戶名不可為空"}
或
```json
{"success":false,"message":"用戶名不可為空,用戶代碼不可為空"}
可以看出,當(dāng)一個(gè)分組內(nèi)有多個(gè)約束,約束的校驗(yàn)順序仍然是隨機(jī)的
6.3 總結(jié)
(1)根據(jù)參數(shù)中的分組對(duì)應(yīng)的接口中@GroupSequence指定的分組類型的順序進(jìn)行加校驗(yàn)
(2)只有當(dāng)一個(gè)分組內(nèi)的所有約束都校驗(yàn)通過(guò)后,才會(huì)進(jìn)入下一個(gè)分組進(jìn)行校驗(yàn)。
(3)順序校驗(yàn),指的是@GroupSequence內(nèi)配置的分組的順序,當(dāng)一個(gè)分組內(nèi)有多個(gè)約束,這個(gè)分組內(nèi)約束的校驗(yàn)順序仍然隨機(jī)
7 驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止
默認(rèn),有多個(gè)約束的情況下,將會(huì)對(duì)所有參數(shù)進(jìn)行校驗(yàn),如果存在校驗(yàn)失敗的約束,返回的校驗(yàn)結(jié)果(BindingResult或?qū)?yīng)Exception)中會(huì)有所有的參數(shù)校驗(yàn)錯(cuò)誤信息。即如果多個(gè)不滿足約束,則返回結(jié)果中會(huì)有多個(gè)失敗信息。
有時(shí)候,我們只需要返回第一個(gè)一個(gè)校驗(yàn)失敗的約束信息就好,校驗(yàn)到一個(gè)約束失敗后,沒(méi)有必要再花費(fèi)代價(jià)進(jìn)行其他約束校驗(yàn)。
springBoot中,參數(shù)校驗(yàn)的實(shí)現(xiàn),基于MethodValidationPostProcessor:
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
@Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}
這個(gè)postProcessor的校驗(yàn)配置基于spring中的beanValidator,我們創(chuàng)建自己的Validator的bean,配置failFast,即可實(shí)現(xiàn)驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止這個(gè)要求。
實(shí)現(xiàn)代碼如下:
@Bean
public Validator validator() {
HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class).configure();
//驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止
configuration.failFast(true);
ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
return validatorFactory.getValidator();
}
或使用更簡(jiǎn)潔的寫(xiě)法:
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
//驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
failFast是HibernateValidatorConfiguration中的一個(gè)屬性配置,配置中還有其他配置屬性,可以定制我們的校驗(yàn)器。
8 自定義校驗(yàn)器
自定義校驗(yàn)器,注意點(diǎn)比較多,不是本文的重點(diǎn),暫時(shí)不進(jìn)行記錄,后續(xù)有時(shí)間會(huì)有專門(mén)的文章進(jìn)行分析。
9 生產(chǎn)環(huán)境配置
前面說(shuō)的都是原理和使用細(xì)節(jié),這里記錄下生產(chǎn)環(huán)境,需要進(jìn)行哪些全局配置。
9.1 全局異常處理
建議使用全局異常處理,對(duì)請(qǐng)求的異常信息進(jìn)行統(tǒng)一處理。
代碼如下:
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.stream.Collectors;
/**
* 統(tǒng)一異常處理
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕捉全局異常:org.springframework.web.bind.MethodArgumentNotValidException
* <div>@RequestBody修飾的參數(shù),校驗(yàn)失敗,拋出此異常</div>
* <div>如:xxxAction(@RequestBody @Valid User user)</div>
* @param exception
* @return
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public JSONObject handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
log.info("全局異常[MethodArgumentNotValidException]:" + exception.getMessage());
JSONObject result = new JSONObject();
result.put("success", false);
if (exception != null) {
String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
result.put("message", message);
}
return result;
}
/**
* 捕捉全局異常:org.springframework.validation.BindException
* <div>普通請(qǐng)求的參數(shù),校驗(yàn)失敗,拋出此異常</div>
* <div>如:xxxAction(@Valid User user)</div>
* @param exception
* @return
*/
@ExceptionHandler(BindException.class)
public JSONObject handlerBindException(BindException exception) {
log.info("全局異常[BindException]:" + exception.getMessage());
JSONObject result = new JSONObject();
result.put("success", false);
if (exception != null) {
String message = exception.getBindingResult().getFieldErrors().stream().filter(e -> e != null).map(FieldError::getDefaultMessage).collect(Collectors.joining(","));
result.put("message", message);
}
return result;
}
/**
* 捕捉全局異常:javax.validation.ConstraintViolationException
* <div>直接在參數(shù)上加的校驗(yàn),校驗(yàn)失敗,拋出此異常</div>
* <div>如:xxxAction(@NotNull(message = "用戶代碼不可為空") String userCode)</div>
* <div>如:xxxAction(@PathVariable("userCode") @Length(max = 10,message="用戶代碼不可超過(guò)10位") String userCode)</div>
* @param exception
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public JSONObject handlerConstraintViolationException(ConstraintViolationException exception) {
log.info("全局異常[ConstraintViolationException]:" + exception.getMessage());
JSONObject result = new JSONObject();
result.put("success", false);
if (exception != null) {
result.put("message", exception.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";")));
}
return result;
}
}
參數(shù)校驗(yàn)失敗,返回的錯(cuò)誤json信息如下,可以根據(jù)項(xiàng)目的實(shí)際情況進(jìn)行定制:
{"success":false,"message":"用戶代碼不可超過(guò)10位"}
9.2 驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
//驗(yàn)證將檢測(cè)到第一個(gè)約束違例時(shí)停止
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
10 補(bǔ)充
10.1 校驗(yàn)限制
@Valid支持嵌套驗(yàn)證、@Validated支持分組驗(yàn)證、排序驗(yàn)證(準(zhǔn)確來(lái)說(shuō),排序驗(yàn)證也是分組驗(yàn)證的一種)。
故無(wú)法實(shí)現(xiàn):“嵌套驗(yàn)證+分組驗(yàn)證”和“嵌套驗(yàn)證+排序驗(yàn)證”這種組合形式的驗(yàn)證。
10.2 建議
雖然JSR303支持自定義校驗(yàn)器,筆者不建議將太復(fù)雜的校驗(yàn)交給JSR303的標(biāo)準(zhǔn)進(jìn)行校驗(yàn)。
如果是參數(shù)基本的屬性校驗(yàn)(是否為空、長(zhǎng)度、大小、枚舉、正則格式),可以以這種形式進(jìn)行校驗(yàn)。
但是如果是太復(fù)雜的校驗(yàn),如需要連接數(shù)據(jù)庫(kù)進(jìn)行業(yè)務(wù)判斷的校驗(yàn),筆者仍然建議在具體的業(yè)務(wù)代碼中進(jìn)行校驗(yàn)。
10.2 校驗(yàn)順序的隨機(jī)性
如不使用@Validated指定約束的校驗(yàn)順序,所有約束的校驗(yàn)順序是隨機(jī)的,即相同的情況,返回的校驗(yàn)結(jié)果的順序可能不一樣。
10.3 一個(gè)字段多個(gè)約束
同一個(gè)字段可以加多個(gè)約束注解,并不是只能有一個(gè)約束注解。如下:
@NotNull(message = "用戶代碼不可為空")
@Length(min = 5, max = 10, message = "用戶代碼長(zhǎng)度需在5~10之間")
private String userCode;
如userCode為空,則拋出異常:用戶代碼不可為空
如userCode不為空,則校驗(yàn)約束:用戶代碼長(zhǎng)度需在5~10之間