團隊新來了個校招實習生靜靜,相互交流后發現竟然是我母校同實驗室的小學妹,小學妹很熱情地認下了我這個失散多年的大濕哥,后來...
小學妹:大濕哥,咱們項目里的 Controller 怎么都看不到參數校驗處理的代碼呀?但是程序運行起來,看到有是有校驗的?
大濕哥:哦哦,靜靜,你看到 Controller 類和方法上的 @Validated
,還有其他參數的 @NotBlank
、@Size
這些注解了嗎?
小學妹:看到了,你的意思是這些注解跟參數校驗的處理有關系?
大濕哥:對呀!是不是覺得咱們項目上 Controller 的代碼特清爽。
小學妹:嗯嗯,很干凈,完全沒有我在學校寫的項目那一大坨校驗的代碼。大濕哥能給我講講是怎么一回事嗎?
大濕哥:好吧!這里是利用了 Bean Validation 的技巧,下面我來詳細講講。
API 是每個 Web 項目中必不可少的部分,后端開發人員除了要處理大量的 CRUD 邏輯之外,接口的參數校驗與響應格式的規范處理也都占用了大量的精力。
在接下來的幾篇文章中,我們將介紹 API 編寫的實戰技巧,讓從請求到響應的接口編寫更加優雅、高效。
這篇我們來討論接口請求參數校驗。
接口常規校驗案例
我們來定義一個用戶對象 - UserDTO,包含用戶名、密碼、性別及地址。地址對象 - AddressDTO,包含省份、城市、詳細地址。
用戶對象的字段有如下約束:
- 用戶名:
- 用戶賬號不能為空
- 賬號長度必須是6-11個字符
- 密碼:
- 密碼長度必須是6-16個字符
- 性別:
- 性別只能為 0:未知,1:男,2:女
- 地址:
- 地址信息不能為空
UserDTO
public class UserDTO {
/**
* 校驗規則:
*
* 1. 用戶賬號不能為空
* 2. 賬號長度必須是6-11個字符
*/
private String name;
/**
* 校驗規則:
* 1. 密碼長度必須是6-16個字符
*/
private String password;
/**
* 校驗規則:
*
* 1. 性別只能為 0:未知,1:男,2:女"
*/
private int sex;
/**
* 校驗規則:
*
* 1. 地址信息不能為空
*/
private AddressDTO address;
// 省略 Getter/Setter
}
AddressDTO
public class AddressDTO {
private String province;
private String city;
private String detail;
// 省略 Getter/Setter
}
UserController
@RestController
@RequestMapping("/users")
public class UserController {
@PostMapping("/create")
public UserDTO create(@RequestBody UserDTO userDTO) {
if (userDTO == null) {
// 此為示例代碼,正式項目中一般不使用 System.out.println 打印日志
System.out.println("用戶信息不能為空");
return null;
}
// 校驗用戶賬戶
String name = userDTO.getName();
if (name == null || name.trim() == "") {
System.out.println("用戶賬號不能為空");
return null;
} else {
int len = name.trim().length();
if (len < 6 || len > 11) {
System.out.println("密碼長度必須是6-11個字符");
return null;
}
}
// 校驗密碼,抽出一個方法,與校驗用戶賬戶的代碼做比較
if (validatePassword(userDTO) == null) {
return null;
}
// 校驗性別
int sex = userDTO.getSex();
if (sex < 0 || sex > 2) {
System.out.println("性別只能為 0:未知,1:男,2:女");
return null;
}
// 校驗地址
validateAddress(userDTO.getAddress());
// 校驗完成后,請求的用戶信息有效,開始處理用戶插入等邏輯,操作成功以后響應。
return userDTO;
}
// 校驗地址,通過拋出異常的方式來處理
private void validateAddress(AddressDTO addressDTO) {
if (addressDTO == null) {
// 也可以通過拋出異常來處理
throw new RuntimeException("地址信息不能為空");
}
validateAddressField(addressDTO.getProvince(), "所在省份不能為空");
validateAddressField(addressDTO.getCity(), "所在城市不能為空");
validateAddressField(addressDTO.getDetail(), "詳細地址不能為空");
}
// 校驗地址中的每個字段,並返回對應的信息
private void validateAddressField(String field, String msg) {
if (field == null || field.equals("")) {
throw new RuntimeException(msg);
}
}
// 將校驗密碼的操作抽取到一個方法中
private UserDTO validatePassword(@RequestBody UserDTO userDTO) {
String password = userDTO.getPassword();
if (password == null || password.trim() == "") {
System.out.println("用戶密碼不能為空");
return null;
} else {
int len = password.trim().length();
if (len < 6 || len > 16) {
System.out.println("賬號長度必須是6-16個字符");
return null;
}
}
return userDTO;
}
}
在 UserController 中,我們定義了創建用戶的接口 /users/create
。在正式開始業務邏輯處理之前,為了保證接收到的參數有效,我們根據規則編寫了大量的校驗代碼,即使我們可以采取抽取方法等重構手段進行復用,但依然需要對校驗規則勞心勞力。
那有沒有什么技巧,能夠避免編寫這大量的參數校驗代碼呢?
Bean Validation 規范與其實現
上面問題的答案當然是:有!
實際上,Java 早在 2009 年就提出了 Bean Validation 規范,該規范定義的是一個運行時的數據驗證框架,在驗證之后驗證的錯誤信息會被馬上返回。並且已經歷經 JSR303、JSR349、JSR380 三次標准的制定,發展到了 2.0。
JSR 規范提案只是提供了規范,並沒有提供具體的實現。具體實現框架有默認的 javax.validation.api,以及 hibernate-validator。目前絕大多使用 hibernate-validator。
javax.validation.api
Java 在 2009 年的 JAVAEE 6 中發布了 JSR303 以及 javax 下的 validation 包內容。這項工作的主要目標是為 java 應用程序開發人員提供 基於 java 對象的 約束(constraints)聲明和對約束的驗證工具(validator),以及約束元數據存儲庫和查詢 API,以及默認實現。
Java8 開始,Java EE 改名為 Jakarta EE,注意 javax.validation 相關的包移動到了 jakarta.validation 的包下。所以大家看不同的版本的時候,會發現以前的版本包在 javax.validation 包下,Java 8之后在 jakarta.validation。
hibernate-validator
hibernate-validator 框架是另外一個針對 Bean Validation 規范的實現,它提供了 JSR 380 規范中所有內置 constraint 的實現,除此之外還有一些附加的 constraint。
使用 validator 進行請求參數校驗實戰
那 Spring Boot 項目中,Bean Validation 的實現框架怎么優雅地解決請求參數校驗問題呢?
接下來,我們開始實戰。我們將繼續采用「接口常規校驗案例」章節中的 UserDTO、AddressDTO,字段的約束一樣。
新建 Spring Boot 項目,引入 spring-boot-start-web
依賴,Spring Boot 2.3.0 之后版本還需要引入 hibernate-validator
,之前的版本已經包含 。
校驗 @RequestBody
注解的參數
要校驗使用 @RequestBody
注解的參數,需要 2 個步驟:
- 對參數對象的字段使用約束注解進行標注
- 在接口中對要校驗的參數對象標注
@Valid
或者@Validated
使用約束注解對字段進行標注
UserDTO
public class UserDTO {
@NotBlank(message = "用戶賬號不能為空")
@Size(min = 6, max = 11, message = "賬號長度必須是6-11個字符")
private String name;
@Size(min = 6, max = 16, message = "密碼長度必須是6-16個字符")
private String password;
@Range(min = 0, max = 2, message = "性別只能為 0:未知,1:男,2:女")
private int sex;
@NotNull(message = "地址信息不能為空")
@Valid
private AddressDTO address;
// 省略 Getter/Setter
}
AddressDTO
public class AddressDTO {
@NotBlank(message = "所在省份不能為空")
private String province;
@NotBlank(message = "所在城市不能為空")
private String city;
@NotBlank(message = "詳細地址不能為空")
private String detail;
// 省略 Getter/Setter
}
可以看到,我們在需要校驗的字段上使用 @NotNull
、@Size
、@Range
等約束注解進行了標注,在 AddressDTO 上還使用了 @Valid
,並且注解中定義了 message
等信息。
為對象類型參數添加 @Validated
現在再來看 UserController 中新建用戶接口的處理(注意:這里是通過 Content-Type: application/json 提交的,請求參數需要在 @RequestBody
中獲取)。我們在需要校驗的 UserDTO 參數前添加 @Validated
注解。省略掉新增用戶的邏輯之后,沒有其他的顯式校驗的代碼。
/**
* 創建用戶,通過 Content-Type: application/json 提交
*
* Validator 校驗失敗將拋出 {@link MethodArgumentNotValidException}
*
* @param userDTO
* @return
*/
@PostMapping("/create-in-request-body")
public UserDTO createInRequestBody(@Validated @RequestBody UserDTO userDTO) {
// 通過 Validator 校驗參數,開始處理用戶插入等邏輯,操作成功以后響應
return userDTO;
}
驗證校驗結果
啟動 Spring Boot 應用,用 Postman 調用請求,觀察結果。
輸入不符合要求的字段后,服務器返回了錯誤的結果。(經試驗:Spring Boot 2.1.4.RELEASE 版本和 Spring Boot 2.3.3.RELEASE 版本輸出結果不一樣,前者輸出還包含 errors 展示具體每個不符合校驗規則的明細。)
在 Idea 的 Console 中可以看到如下日志,這說明不符合校驗規則的參數已經被驗證。
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public io.ron.demo.validator.use.dto.UserDTO io.ron.demo.validator.use.controller.UserController.createInRequestBody(io.ron.demo.validator.use.dto.UserDTO) with 3 errors: [Field error in object 'userDTO' on field 'password': rejected value [123]; codes [Size.userDTO.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.password,password]; arguments []; default message [password],16,6]; default message [密碼長度必須是6-16個字符]] [Field error in object 'userDTO' on field 'name': rejected value [lang1]; codes [Size.userDTO.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.name,name]; arguments []; default message [name],11,6]; default message [賬號長度必須是6-11個字符]] [Field error in object 'userDTO' on field 'sex': rejected value [3]; codes [Range.userDTO.sex,Range.sex,Range.int,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.sex,sex]; arguments []; default message [sex],2,0]; default message [性別只能為 0:未知,1:男,2:女]] ]
如果請求參數都滿足條件,則能正確響應結果。
校驗不使用 @RequestBody
注解的對象
有時我們也會編寫這樣的接口,接口方法中的對象類型參數不使用 @RequestBody
注解。使用 @RequestBody
注解的對象類型參數需要明確以 Content-Type: application/json 上傳。而這種寫法可以以 Content-Type: application/x-www-form-urlencoded 上傳。
這樣編寫接口,校驗方法與被 @RequestBody
注解的對象參數一樣。
驗證校驗結果
在 Idea 的 Console 中可以看到如下日志:
2020-09-18 17:56:05.191 WARN 9734 --- [nio-9001-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 2 errors
Field error in object 'userDTO' on field 'name': rejected value [12345]; codes [Size.userDTO.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.name,name]; arguments []; default message [name],11,6]; default message [賬號長度必須是6-11個字符]
Field error in object 'userDTO' on field 'address.detail': rejected value [ ]; codes [NotBlank.userDTO.address.detail,NotBlank.address.detail,NotBlank.detail,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.address.detail,address.detail]; arguments []; default message [address.detail]]; default message [詳細地址不能為空]]
請注意日志中的異常類型與 @RequestBody 注解的校驗異常的區別。
這里報的異常類型是:
org.springframework.validation.BindException
,而上一節中報的異常類型是:
org.springframework.web.bind.MethodArgumentNotValidException
。在下一節中的情形,報的異常則是:
javax.validation.ConstraintViolationException
。異常的處理我們將在下一篇文章中說明。請關注我的公眾號:精進Java(ID:craft4j),第一時間獲取知識動態。
校驗 @PathVariable
與 @RequestParam
注解的參數
在真實項目中,不是所有的接口都接受對象類型的參數,如分頁接口中的頁碼會使用 @RequestParam
注解;Restful 風格的接口會通過 @PathVariable
來獲取資源 ID 等。這些參數無法通過上面的方法被 validator 校驗。
要校驗 @PathVariable
與 @RequestParam
注解的參數,需要 2 個步驟:
- 在要校驗的接口類上標注
@Validated
注解 - 在簡單類型參數前標注
@PathVariable
或@RequestParam
/**
* 測試 @PathVariable 參數的校驗
*
* Validator 校驗失敗將拋出 {@link ConstraintViolationException}
*
* @param id
* @return
*/
@GetMapping("/user/{id}")
public UserDTO retrieve(@PathVariable("id") @Min(value = 10, message = "id 必須大於 10") Long id) {
return buildUserDTO("lfy", "qwerty", 1);
}
/**
* 測試 @RequestParam 參數校驗
*
* 在方法上加 @Validated 無法校驗 @RequestParam 與 @PathVariable
*
* 必須在類上 @Validated
*
* Validator 校驗失敗將拋出 {@link ConstraintViolationException}
*
* @param name
* @param password
* @param sex
* @return
*/
// @Validated
@GetMapping("/validate")
public UserDTO validate(@NotNull @Size(min = 6, max = 11, message = "賬號長度必須是6-11個字符") @RequestParam("name") String name,
@RequestParam("password") @Size(min = 6, max = 16, message = "密碼長度必須是6-16個字符") String password,
@RequestParam("sex") @Range(min = 0, max = 2, message = "性別只能為 0:未知,1:男,2:女") int sex) {
return buildUserDTO(name, password, sex);
}
private UserDTO buildUserDTO(String name, String password, int sex) {
UserDTO userDTO = new UserDTO();
userDTO.setName(name);
userDTO.setPassword(password);
userDTO.setSex(sex);
return userDTO;
}
驗證 @PathVariable
校驗結果
輸入小於 10 的 id,結果如下:
Idea 中 Console 報錯誤日志如下:
2020-09-18 17:23:55.744 ERROR 9734 --- [nio-9001-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintViolationException: retrieve.id: id 必須大於 10] with root cause
javax.validation.ConstraintViolationException: retrieve.id: id 必須大於 10
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116) ~[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE]
......
驗證 @RequestParam
校驗結果
輸入不滿足要求的參數 name 和 password
Idea 中 Console 報錯誤日志如下:
2020-09-18 17:37:51.875 ERROR 9734 --- [nio-9001-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintViolationException: validate.password: 密碼長度必須是6-16個字符, validate.name: 賬號長度必須是6-11個字符] with root cause
javax.validation.ConstraintViolationException: validate.password: 密碼長度必須是6-16個字符, validate.name: 賬號長度必須是6-11個字符
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:116) ~[spring-context-5.2.8.RELEASE.jar:5.2.8.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.2.8.RELEASE.jar:5.2.8.RELEASE]
......
分組校驗
還有這樣的場景,如在新建用戶的接口中,用戶的 ID 字段為空;而在更新用戶的接口中,用戶的 ID 字段則要求是必填。針對同一個用戶實體對象,我們可以利用 validator 提供的分組校驗。
分組校驗的步驟如下:
- 定義分組接口。
- 對字段使用約束注解進行標注,使用 groups 參數進行分組。
- 用
@Validated
標識參數,並設置 groups 參數。
普通分組
Update:我們定義一個 Update
分組接口
public interface Update {
}
UserDTO:對字段上的約束注解添加 groups 參數。下面對 id 的 @NotNull
添加了 Update
分組,對 name 字段的 @NotBlank
添加了 Update
分組及 validator 的默認 Default
分組。
@NotNull(message = "用戶ID不能為空", groups = { Update.class })
private Long id;
@NotBlank(message = "用戶賬號不能為空", groups = { Update.class, Default.class })
@Size(min = 6, max = 11, message = "賬號長度必須是6-11個字符")
private String name;
UserController:在更新用戶接口中對參數使用 @Validated
注解並設置 Update
分組。
@PutMapping("/update")
public UserDTO update(@Validated({Update.class}) @RequestBoy UserDTO userDTO) {
// 通過 Validator 校驗參數,開始處理用戶插入等邏輯,操作成功以后響應
return userDTO;
}
這里將只會對設置了分組為 Update
的約束進行校驗,當 id 為空或者 name 為空或者空白的時候會報約束錯誤。當 id 與 name 均不為空時,即使 name 的長度不在 6-11 個字符之間,也不會校驗。
組序列
除了按組指定是否驗證之外,還可以指定組的驗證順序,前面組驗證不通過的,后面組將不進行驗證。
OrderedGroup:定義了校驗組的順序,Update
優先於 Default
。
@GroupSequence({ Update.class, Default.class })
public interface OrderedGroup {
}
UserController:在接口參數中 @Validated
注解設置參數 OrderedGroup.class
。
@PostMapping("/ordered")
public UserDTO ordered(@Validated({OrderedGroup.class}) @RequestBody UserDTO userDTO) {
// 通過 Validator 校驗參數,開始處理用戶插入等邏輯,操作成功以后響應
return userDTO;
}
與普通分組中的案例結果不同,這里會優先校驗 id 是否為空或者 name 是否為空或者空白,即 Update
分組的約束;Update
分組約束滿足之后,還會進行其他參數的校驗,因為其他參數都默認為 Default
分組。
hibernate-validator 的校驗模式
從上面的案例中,細心的你可能已經發現了這樣的現象:所有的參數都做了校驗。實際上只要有一個參數校驗不通過,我們就可以響應給用戶。而 hibernate-validator 可以支持兩種校驗模式:
- 普通模式,默認是這種模式,該模式會校驗完所有的屬性,然后返回所有的驗證失敗信息
- 快速失敗返回模式,這種模式下只要有一個參數校驗失敗就立即返回
開啟快速失敗返回模式
@Configuration
public class ValidatorConfig {
@Value("${hibernate.validator.fail_fast:false}")
private boolean failfast;
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// failFast 為 true 時,只要出現校驗失敗的情況,就立即結束校驗,不再進行后續的校驗。
.failFast(failfast)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
// 設置 validator 模式為快速失敗返回
postProcessor.setValidator(validator());
return postProcessor;
}
}
我們需要對 MethodValidationPostProcessor 設置開啟快失敗返回模式的 validator。而 validator 則只需設置 hibernate.validator.fail_fast
屬性為 true。
再次運行 Spring Boot 項目,進行測試,我們會發現現在只要有一個參數校驗失敗,就立即返回了。
自定義約束實現
vaidation-api 與 hibernate-validator 提供的約束注解已經能夠滿足我們絕大多數的參數校驗要求,但有時我們可能也需要使用自定義的 Validator 校驗器。
自定義約束實現與使用包含如下步驟:
- 自定義約束注解
- 實現
ConstraintValidator
來自定義校驗邏輯
通用枚舉類型約束
我們以自定義一個相對通用的枚舉類型約束來演示。
自定義 @EnumValue
枚舉指約束注解
enumClass
標識字段取值對應哪個枚舉類型,enumMethod
則是需要枚舉類定義一個用於驗證取值是否有效的驗證方法,如果為空的話,我們會默認提供處理參數為整型與字符串型的情況。需要在注解上使用 @Constraint(validatedBy)
來設置具體使用的校驗器。
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValue.EnumValidator.class)
public @interface EnumValue {
String message() default "無效的枚舉值";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
Class<? extends Enum<?>> enumClass();
String enumMethod() default "";
}
編寫 EnumValidator
來自定義校驗邏輯
class EnumValidator implements ConstraintValidator<EnumValue, Object> {
private Class<? extends Enum<?>> enumClass;
private String enumMethod;
@Override
public void initialize(EnumValue enumValue) {
enumMethod = enumValue.enumMethod();
enumClass = enumValue.enumClass();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
if (value == null) {
return Boolean.TRUE;
}
if (enumClass == null) {
return Boolean.TRUE;
}
Class<?> valueClass = value.getClass();
if (enumMethod == null || enumMethod.equals("")) {
String valueClassName = valueClass.getCanonicalName();
// 處理參數可以轉為枚舉值 ordinal 的情況
if (valueClassName.equals("java.lang.Integer")) {
return enumClass.getEnumConstants().length > (Integer) value;
}
// 處理參數為枚舉名稱的情況
else if (valueClassName.equals("java.lang.String")) {
return Arrays.stream(enumClass.getEnumConstants()).anyMatch(e -> e.toString().equals(value));
}
throw new RuntimeException(String.format("A static method to valid enum value is needed in the %s class", enumClass));
}
// 枚舉類自定義取值校驗
try {
Method method = enumClass.getMethod(enumMethod, valueClass);
if (!Boolean.TYPE.equals(method.getReturnType()) && !Boolean.class.equals(method.getReturnType())) {
throw new RuntimeException(String.format("%s method return is not boolean type in the %s class", enumMethod, enumClass));
}
if (!Modifier.isStatic(method.getModifiers())) {
throw new RuntimeException(String.format("%s method is not static method in the %s class", enumMethod, enumClass));
}
Boolean result = (Boolean) method.invoke(null, value);
return result == null ? false : result;
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(String.format("This %s(%s) method does not exist in the %s", enumMethod, valueClass, enumClass), e);
}
}
}
實現 ConstraintValidator 的兩個方法 initialize
、isValid
,一個是初始化參數的方法,另一個就是校驗邏輯的方法。
在校驗方法中,當約束注解沒有定義 enumMethod
時,我們根據傳入需要校驗的參數提供整型與字符型的兩種默認校驗,可以仔細看源碼第 23-34 行。從 37-54 行的代碼可以看到,除開上面的 2 種情況下,枚舉類型需要提供一個自定義的校驗方法。
在項目中使用
Gender:我們定義一個表示性別的枚舉。這里我們編寫了一個判斷枚舉取值是否有效的靜態方法 isValid()
。
public enum Gender {
UNKNOWN,
MALE,
FEMALE;
/**
* 判斷取值是否有效
*
* @param val
* @return
*/
public static boolean isValid(Integer val) {
return Gender.values().length > val;
}
}
UserDTO:給 sex 字段的設置 @EnumValue
約束
@Range(min = 0, max = 2, message = "性別只能為 0:未知,1:男,2:女")
@EnumValue(enumClass = Gender.class)
private int sex;
接下來就是運行程序,發請求觀察校驗結果了。
總結
前面我們從一個常規校驗案例開始,說明了 Bean Validation 規范及其實現,並從實戰角度出發介紹了各種場景下的校驗,包括:
- 使用
@RequestBody
和不使用@RequestBody
注解的對象類型參數 - 使用
@RequestParam
和@PathVariable
注解的簡單類型參數 - 分組校驗
- 快速失敗返回校驗模式
- 自定義約束校驗
總體而言,使用 validator 能夠極大的方便請求參數的校驗,簡化校驗相關的實現代碼。但是,細心的讀者也發現了,本文中所有的接口當有參數校驗失敗時,都是報了異常,返回的響應中直接報 400 或者 500 的錯誤。響應不直觀,不規范,給到前端也無法方便高效的處理。
在接下來的文章中,我將繼續為大家帶來全局異常處理、統一響應結構的知識與實戰。
文中涉及的代碼已經開源在我的 Github 倉庫 ron-point 中。如果覺得不錯請點個 star,歡迎一起討論和交流。