1.什麼是Bean Validation?
Bean Validation 是一個執行時的資料驗證框架,在驗證之後驗證的錯誤資訊會被馬上返回。java 在2009年的 JAVAEE 6 中釋出了 JSR303以及javax下的validation包內容。這項工作的主要目標是為java應用程式開發人員提供 基於java物件的 約束(constraints)宣告 和 對約束的驗證工具(validator),以及約束後設資料儲存庫和查詢API。但是該內容並沒有具體的實現, Hibernate-Validator框架 提供了 JSR 303 規範中所有內建 constraint 的實現,除此之外還有一些附加的 constraint。
常用的Validation註解
一些最常見的驗證註解如下:
@NotNull: 標記欄位不能為 null
@NotEmpty: 標記集合欄位不為空(至少要有一個元素)
@NotBlank: 標記欄位串欄位不能是空字串(即它必須至少有一個字元)
@Min / @Max: 標記數字型別欄位必須大於/小於指定的值
@Pattern: 標記字串欄位必須匹配指定的正規表示式
@Email: 標記字串欄位必須是有效的電子郵件地址
2.程式碼工程
實驗目標:校驗controller傳遞的引數
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springboot-demo</artifactId> <groupId>com.et</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>validtion</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency> </dependencies> </project>
controller
Spring 自動將傳入的 JSON 對映到 Java 物件引數上。 現在,我們要校驗傳入的 Java 物件是否滿足我們預先定義的約束條件。 爲了校驗傳入 HTTP 請求的請求實體,我們在 REST 控制器中使用 @Valid 註解對請求實體進行標記:
package com.et.validation.controller; import cn.hutool.json.JSON; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.et.validation.entity.UserInfoReq; import com.fasterxml.jackson.databind.util.JSONPObject; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController public class HelloWorldController { @RequestMapping("/hello") public Map<String, Object> showHelloWorld(@RequestBody @Validated UserInfoReq req){ Map<String, Object> map = new HashMap<>(); map.put("msg", JSONUtil.toJsonStr(req)); return map; } }
enttiy
我們有一個 int型別 欄位,它的值必須介於 1 和 10200之間,如@Min 和@Max 註解所定義的那樣。 我們還有一個 String 型別欄位,它必須是一個 IP 地址,正如@Pattern 註解中的正規表示式所定義的那樣(正規表示式實際上仍然允許大於 255 的無效 IP 地址,但在我們建立自定義驗證器時,我們將在本教程的後面修復這個BUG)。
package com.et.validation.entity; import com.et.validation.validate.IpAddress; import lombok.Data; import lombok.ToString; import javax.validation.constraints.*; @Data @ToString public class UserInfoReq { @NotNull(message = "id不能為null") private Long id; @NotBlank(message = "username不能為空") private String username; @NotNull(message = "age不能為null") @Min(value = 1, message = "年齡不符合要求") @Max(value = 200, message = "年齡不符合要求") private Integer age; @Email(message = "郵箱不符合規範") private String email; @IpAddress(message = "ip不符合規範") //@Pattern(regexp = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$") private String ip; }
自定義error捕獲
當校驗失敗時,我們通常希望向客戶端返回一條有意義的錯誤訊息。 爲了使客戶端能夠顯示有用的錯誤訊息,我們應該返回一個統一的數據結構,其中包含每個校驗失敗的錯誤訊息。 我們在這裏所做的只是從異常中讀取有關校驗失敗資訊並將它們轉換到我們的 ValidationErrorResponse 數據結構中。 請注意@ControllerAdvice 註解,它使得上述型別異常的異常處理機制對所有Controller全域性可用。
package com.et.validation.error; import org.springframework.http.HttpStatus; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; @ControllerAdvice class ErrorHandlingControllerAdvice { @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody ValidationErrorResponse onConstraintValidationException( ConstraintViolationException e) { ValidationErrorResponse error = new ValidationErrorResponse(); for (ConstraintViolation violation : e.getConstraintViolations()) { error.getViolations().add( new Violation(violation.getPropertyPath().toString(), violation.getMessage())); } return error; } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody ValidationErrorResponse onMethodArgumentNotValidException( MethodArgumentNotValidException e) { ValidationErrorResponse error = new ValidationErrorResponse(); for (FieldError fieldError : e.getBindingResult().getFieldErrors()) { error.getViolations().add( new Violation(fieldError.getField(), fieldError.getDefaultMessage())); } return error; } }
package com.et.validation.error; import lombok.Data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Data public class ValidationErrorResponse implements Serializable { private List<Violation> violations = new ArrayList<>(); }
package com.et.validation.error; import lombok.Data; import java.io.Serializable; @Data public class Violation implements Serializable { private final String fieldName; private final String message; public Violation(String fieldName, String message) { this.fieldName = fieldName; this.message = message; } }
自定義validator
如果官方提供可用的校驗註解不能滿足我們的需要,我們自己可以定義一個自定義校驗器。 自定義校驗註解需要包含以下要素:
引數message, 指定 ValidationMessages.properties 檔案中的屬性鍵,用於在校驗失敗時解析提示訊息,
引數 groups, 允許定義在何種情況下觸發此校驗(稍後我們將討分組校驗)
引數payload, 允許定義要透過此校驗傳遞的Payload(因為這是一個很少使用的功能,我們不會在本教程中介紹它)
一個 @Constraint 註解, 指定實現 ConstraintValidator 介面的校驗邏輯類。
校驗器的實現如下所示:
package com.et.validation.validate; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({ FIELD }) @Retention(RUNTIME) @Constraint(validatedBy = IpAddressValidator.class) @Documented public @interface IpAddress { String message() default "{IpAddress.invalid}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
package com.et.validation.validate; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Matcher; import java.util.regex.Pattern; class IpAddressValidator implements ConstraintValidator<IpAddress, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { Pattern pattern = Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$"); Matcher matcher = pattern.matcher(value); try { if (!matcher.matches()) { return false; } else { for (int i = 1; i <= 4; i++) { int octet = Integer.valueOf(matcher.group(i)); if (octet > 255) { return false; } } return true; } } catch (Exception e) { return false; } } }
以上只是一些關鍵程式碼,所有程式碼請參見下面程式碼倉庫
程式碼倉庫
https://github.com/Harries/springboot-demo
3.測試
啟動spring boot應用
測試引數校驗介面
訪問http://127.0.0.1:8088/hello,返回結果如下
4.引用
https://reflectoring.io/bean-validation-with-spring-boot/