@validate或@valid注解進行資料校驗的解決方案
目錄- @validate或@valid注解進行資料校驗的解決方案
- JSR規范提案
- 依賴引入
- JSR303定義的校驗型別
- @Valid和@Validated的區別
- 注解位置
- 分組
- 嵌套驗證
- 使用BindingResult接收校驗結果資訊
- 統一例外處理
我們在對外提供介面的時候,為了提高安全性,我們需要在后端做資料的校驗,實際上,Java 早在 2009 年就提出了 Bean Validation 規范,該規范定義的是一個運行時的資料驗證框架,在驗證之后驗證的錯誤資訊會被馬上回傳,并且已經歷經 JSR303、JSR349、JSR380 三次標準的置頂,發展到了 2.0 ,下面即將要介紹的是該資料驗證的規范,以及相應的技術框架日常使用,
JSR規范提案
JSR:Java Specification Requests的縮寫,意思是Java 規范提案,是指向JCP(Java Community Process)提出新增一個標準化技術規范的正式請求,任何人都可以提交JSR,以向Java平臺增添新的API和服務,JSR已成為Java界的一個重要標準,
本文介紹的Bean Validation 就是出自JSR303,JSR349,以及JSR380 規范提案,該規范從JSR 303 發展到 JSR 380,目前最新規范是Bean Validation 2.0,
相信有小伙伴想去看下到底是個啥,規范提案地址:https://jcp.org/en/jsr/summary?id=bean+validation
需要注意的是,規范提案只是提供了規范,并沒有提供具體的實作,具體實作框架有默認的javax.validation.api,以及hibernate-validator,目前絕大多使用hibernate-validator,
依賴引入
要使用注解進行校驗,需要引入如下兩個依賴:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.20.Final</version>
</dependency>
據我發現: 2.2.9.RELEASE 版的 spring-boot-starter-web 中引入了 spring-boot-starter-validation ,而 spring-boot-starter-validation 中又引入了 hibernate-validator ,所以如果引入了 spring-boot-starter-web 或者 spring-boot-starter-validation 都已經默認引入了 hibernate-validator 可以不用再引,
其它版本還未研究過,在使用的時候可以自己研究一下,
JSR303定義的校驗型別
空檢查
@Null 驗證物件是否為null
@NotNull 驗證物件是否不為null, 無法查檢長度為0的字串
@NotBlank 檢查約束字串是不是Null還有被Trim的長度是否大于0,只對字串,且會去掉前后空格.
@NotEmpty 檢查約束元素是否為NULL或者是EMPTY.
Booelan檢查
@AssertTrue 驗證 Boolean 物件是否為 true
@AssertFalse 驗證 Boolean 物件是否為 false
長度檢查
@Size(min=, max=) 驗證物件(Array,Collection,Map,String)長度是否在給定的范圍之內
@Length(min=, max=) 驗證注解的元素值長度在min和max區間內
日期檢查
@Past 驗證 Date 和 Calendar 物件是否在當前時間之前
@Future 驗證 Date 和 Calendar 物件是否在當前時間之后
@Pattern 驗證 String 物件是否符合正則運算式的規則
數值檢查,建議使用在Stirng,Integer型別,不建議使用在int型別上,因為表單值為“”時無法轉換為int,但可以轉換為Stirng為"",Integer為null
@Min 驗證 Number 和 String 物件是否大等于指定的值
@Max 驗證 Number 和 String 物件是否小等于指定的值
@DecimalMax 被標注的值必須不大于約束中指定的最大值. 這個約束的引數是一個通過BigDecimal定義的最大值的字串表示.小數存在精度
@DecimalMin 被標注的值必須不小于約束中指定的最小值. 這個約束的引數是一個通過BigDecimal定義的最小值的字串表示.小數存在精度
@Digits 驗證 Number 和 String 的構成是否合法
@Digits(integer=,fraction=) 驗證字串是否是符合指定格式的數字,interger指定整數精度,fraction指定小數精度,
@Range(min=, max=) 驗證注解的元素值在最小值和最大值之間
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;
@Valid 遞回的對關聯物件進行校驗, 如果關聯物件是個集合或者陣列,那么對其中的元素進行遞回校驗,如果是一個map,則對其中的值部分進行校驗.(是否進行遞回驗證)
@CreditCardNumber信用卡驗證
@Email 驗證是否是郵件地址,如果為null,不進行驗證,算通過驗證,
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
@Valid和@Validated的區別
@Valid注解是javax提供的,遵循標準 JSR-303 規范,所屬包為: javax.validation.Valid
配合BindingResult可以直接提供引數驗證結果,
@Validated是@Valid的一次封裝,是Spring提供的校驗機制使用,遵循 Spring’s JSR-303 規范(是標準 JSR-303 的一個變種),所屬包為: org.springframework.validation.annotation.Validated
@Validation對@Valid進行了二次封裝,在基本使用上并沒有區別,但在分組、注解位置、嵌套驗證等功能上有所不同,這里主要就這幾種情況進行說明,
注解位置
@Validated:可以用在型別、方法和方法引數上,但是不能用在成員屬性(欄位)上
@Valid:可以用在方法、建構式、方法引數和成員屬性(欄位)上
兩者是否能用于成員屬性(欄位)上直接影響能否提供嵌套驗證的功能,
分組
先定義分組介面(介面什么都不需要,空的就可以):
public interface Insert {
}
public interface Update {
}
在需要校驗的bean上加上分組注解:
@NotBlank(groups = {Update.class}, message = "ID不能為空")
private String id;
@NotBlank(groups = {Insert.class, Update.class}, message = "名稱不能為空")
@Size(groups = {Insert.class, Update.class}, max = 32, message = "名稱最大長度為32")
private String name;
根據需要,在Controller處理請求中加入 @Validated 并引入需要校驗的分組(未引入分組則都校驗)
@PostMapping("/insert")
public int insert(@RequestBody @Validated({Insert.class}) HospitalRequest request) {
return hospitalService.insert(request);
}
@PostMapping("/update")
public int update(@RequestBody @Validated({Update.class}) HospitalRequest request) {
return hospitalService.update(request);
}
在進行insert的時候不會對id進行校驗
嵌套驗證
嵌套驗證就是類嵌套類的驗證,比如我要在集合上加一個@NotNull的注解,要求該集合中的每一個物件都被驗證,如果只用@Validated與@Valid是不會驗證的,我們要用@Validated配合@Valid來進行驗證,
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class HospitalRequest {
/**
* ID
*/
@NotBlank(groups = {Update.class}, message = "ID不能為空")
private String id;
/**
* 名稱
*/
@NotBlank(groups = {Insert.class, Update.class}, message = "名稱不能為空")
@Size(groups = {Insert.class, Update.class}, max = 32, message = "名稱最大長度為32")
private String name;
/**
* 科室
*/
@NotBlank(message = "departmentList不能為空")
@Size(min = 1, message = "至少要有一個屬性")
private List<Department> departmentList;
}
例如我想讓departmentList中的每一個元素都按照我規定的JSR-303校驗進行驗證,
那么我在controller中不管用@Validated還是@Valid都是不能驗證的,
只需要在前面加上@Validated注解
@PostMapping("/add")
public void add(@RequestBody @Validated HospitalRequest request) {
add();
}
然后把@Valid放到需要驗證的集合上就可以了:
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class HospitalRequest {
/**
* ID
*/
@NotBlank(groups = {Update.class}, message = "ID不能為空")
private String id;
/**
* 名稱
*/
@NotBlank(groups = {Insert.class, Update.class}, message = "名稱不能為空")
@Size(groups = {Insert.class, Update.class}, max = 32, message = "名稱最大長度為32")
private String name;
/**
* 科室
*/
@Valid //嵌套驗證必須用@Valid
@NotBlank(groups = {Insert.class, Update.class}, message = "departmentList不能為空")
@Size(groups = {Insert.class, Update.class}, min = 1, message = "至少要有一個屬性")
private List<Department> departmentList;
}
使用BindingResult接收校驗結果資訊
使用注解進行校驗的時候,我們可以通過BindingResult來收集校驗結果資訊,具體操作如下:
Controller中,在@Valid或@Validated修飾的引數后跟上BindingResult引數(@Valid或@Validated 和 BindingResult 是一 一對應的,如果有多個@Valid或@Validated,那么每個@Valid或@Validated后面都需要添加BindingResult用于接收bean中的校驗資訊)
@PostMapping("/insert")
public int insert(@RequestBody @Validated HospitalRequest request, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
List<String> collect = bindingResult.getFieldErrors().stream().map(
DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
StringBuilder errorMsg = new StringBuilder();
for (String s : collect) {
errorMsg.append(s);
errorMsg.append(",");
}
errorMsg = new StringBuilder(errorMsg.substring(0, errorMsg.length() - 1));
log.error("校驗未通過:{}", errorMsg.toString());
Assert.state(Boolean.FALSE, errorMsg.toString());
}
return hospitalService.insert(request);
}
這樣就可以接收到校驗的結果資訊,可以根據校驗的結果資訊進行一系列操作,如列印錯誤資訊、拋出指定例外等,
統一例外處理
在日常開發中,我們可能需要讓校驗回傳指定的資訊或物件,這時我們就可以進行統一例外處理:
package com.app.config;
import com.framework.common.domain.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.List;
/**
* 引數校驗例外處理
*/
@Slf4j
@RestControllerAdvice
public class BadRequestExceptionHandler {
/**
* 校驗錯誤攔截處理
*
* @param exception 錯誤資訊集合
* @return ErrorResponse 錯誤回應,當HTTP回應狀態碼不為200時,使用該回應回傳
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
private ErrorResponse validateRequestException(MethodArgumentNotValidException exception) {
BindingResult bindingResult = exception.getBindingResult();
StringBuilder errorMsg = new StringBuilder();
if (bindingResult.hasErrors()) {
List<ObjectError> errors = bindingResult.getAllErrors();
for (ObjectError objectError : errors) {
FieldError fieldError = (FieldError) objectError;
if (log.isDebugEnabled()) {
log.error("Data check failure : object: {},field: {},errorMessage: {}",
fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
}
errorMsg.append(objectError.getDefaultMessage());
errorMsg.append(",");
}
errorMsg = new StringBuilder(errorMsg.substring(0, errorMsg.length() - 1));
}
return new ErrorResponse("ILLEGAL_ARGUMENT_ERROR", errorMsg.toString());
}
}
回傳的自定義回應體如下:
package com.framework.common.domain;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 錯誤回應,當HTTP回應狀態碼不為200時,使用該回應回傳
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ErrorResponse {
/**
* 錯誤碼
*/
private String code;
/**
* 錯誤資訊
*/
private String message;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231287.html
標籤:Java
