數據字段通常都要遵循業務要求和數據庫設計,因此後端的參數校驗是必須的,應用程序必須經過某種手段來確保輸入進來的數據從語義上來說是正確的。java
爲了保證數據語義的正確,咱們須要進行大量的判斷來處理驗證邏輯。並且項目的分層也會形成一些重複的校驗,產生大量與業務無關的代碼。不利於代碼的維護,增長了開發人員的工做量。git
爲了解決上面的痛點,將驗證邏輯與相應的領域模型進行綁定是十分有必要的。爲此產生了JSR 303 – Bean Validation 規範。[Hibernate Validator]() 是JSR-303的參考實現,它提供了JSR 303規範中全部的約束(constraint)的實現,同時也增長了一些擴展。正則表達式
約束註解 | 詳細信息 |
---|---|
@Null |
被註釋的元素必須爲 null |
@NotNull |
被註釋的元素必須不爲 null |
@AssertTrue |
被註釋的元素必須爲 true |
@AssertFalse |
被註釋的元素必須爲 false |
@Min(value) |
被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@Max(value) |
被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@DecimalMin(value) |
被註釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@DecimalMax(value) |
被註釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@Size(max, min) |
被註釋的元素的大小必須在指定的範圍內 |
@Digits (integer, fraction) |
被註釋的元素必須是一個數字,其值必須在可接受的範圍內 |
@Past |
被註釋的元素必須是一個過去的日期 |
@Future |
被註釋的元素必須是一個未來的日期 |
@Pattern(value) |
被註釋的元素必須符合指定的正則表達式 |
@Email |
被註釋的元素必須是電子郵箱地址 |
@Length |
被註釋的字符串的大小必須在指定的範圍內 |
@NotEmpty |
被註釋的字符串的必須非空 |
@Range |
被註釋的元素必須在合適的範圍內 |
在Spring Boot開發中使用Hibernate Validator是很是容易的,引入下面的starter就能夠了:spring
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
一種能夠實現接口來定製Validator
,一種是使用約束註解。胖哥以爲註解能夠知足絕大部分的需求,因此建議使用註解來進行數據校驗。並且註解更加靈活,控制的粒度也更加細。接下來咱們來學習如何使用註解進行數據校驗。數據庫
咱們對須要校驗的方法入參進行註解約束標記,例子以下:json
@Data public class Student { @NotBlank(message = "姓名必須填") private String name; @NotNull(message = "年齡必須填寫") @Range(min = 1,max =50, message = "年齡取值範圍1-50") private Integer age; @NotEmpty(message = "成績必填") private List<Double> scores; }
而後定義一個POST請求的Spring MVC接口:後端
@RestController @RequestMapping("/student") public class StudentController { @PostMapping("/add") public Rest<?> addStudent(@Valid @RequestBody Student student) { return RestBody.okData(student); } }
經過對addStudent
方法入參添加@Valid
來啓用參數校驗。當使用下面數據進行請求將會拋出MethodArgumentNotValidException
異常,提示age
範圍超出1-50
。app
POST /student/add HTTP/1.1 Host: localhost:8888 Content-Type: application/json { "name": "felord.cn", "age": 77, "scores": [ 55 ] }
如法炮製,咱們定義一個GET請求的接口:框架
@GetMapping("/get") public Rest<?> getStudent(@Valid Student student) { return RestBody.okData(student); }
使用下面的請求能夠正確對學生分數scores
進行了校驗,可是拋出的並非MethodArgumentNotValidException
異常,而是BindException
異常。這和使用@RequestBody
註解有關係,這對咱們後面的統一處理很是十分重要。數據庫設計
GET /student/get?name=felord.cn&age=12 HTTP/1.1 Host: localhost:8888
可能有些同窗注意到上面的年齡我進行了這樣的標記:
@NotNull(message = "年齡必須填寫") @Range(min = 1,max =50, message = "年齡取值範圍1-50") private Integer age;
這是由於@Range
不會去校驗爲空的狀況,它只處理非空的時候是否符合範圍約束。因此要用多個註解來約束。若是咱們某些場景須要重複的捆綁多個註解來使用時,可使用自定義註解將它們封裝起來組合使用,下面這個註解就是將@NotNull
和@Range
進行了組合,你能夠仿一個出來用用看。
import org.hibernate.validator.constraints.Range; import javax.validation.Constraint; import javax.validation.Payload; import javax.validation.ReportAsSingleViolation; import javax.validation.constraints.NotNull; import javax.validation.constraintvalidation.SupportedValidationTarget; import javax.validation.constraintvalidation.ValidationTarget; import java.lang.annotation.*; /** * @author a * @since 17:31 **/ @Constraint( validatedBy = {} ) @SupportedValidationTarget({ValidationTarget.ANNOTATED_ELEMENT}) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @NotNull @Range(min = 1, max = 50) @Documented @ReportAsSingleViolation public @interface Age { // message 必須有 String message() default "年齡必須填寫,且範圍爲 1-50 "; // 可選 Class<?>[] groups() default {}; // 可選 Class<? extends Payload>[] payload() default {}; }
還有一種狀況,咱們在後臺定義了枚舉值來進行狀態的流轉,也是須要校驗的,好比咱們定義了顏色枚舉:
public enum Colors { RED, YELLOW, BLUE }
咱們但願入參不能超出Colors
的範圍["RED", "YELLOW", "BLUE"]
,這就須要實現ConstraintValidator<A extends Annotation, T>
接口來定義一個顏色約束了,其中泛型A
爲自定義的約束註解,泛型T
爲入參的類型,這裏使用字符串,而後咱們的實現以下:
/** * @author felord.cn * @since 17:57 **/ public class ColorConstraintValidator implements ConstraintValidator<Color, String> { private static final Set<String> COLOR_CONSTRAINTS = new HashSet<>(); @Override public void initialize(Color constraintAnnotation) { Colors[] value = constraintAnnotation.value(); List<String> list = Arrays.stream(value) .map(Enum::name) .collect(Collectors.toList()); COLOR_CONSTRAINTS.addAll(list); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { return COLOR_CONSTRAINTS.contains(value); } }
而後聲明對應的約束註解Color
,須要在元註解@Constraint
中指明使用上面定義好的處理類ColorConstraintValidator
進行校驗。
/** * @author felord.cn * @since 17:55 **/ @Constraint(validatedBy = ColorConstraintValidator.class) @Documented @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface Color { // 錯誤提示信息 String message() default "顏色不符合規格"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 約束的類型 Colors[] value(); }
而後咱們來試一下,先對參數進行約束:
@Data public class Param { @Color({Colors.BLUE,Colors.YELLOW}) private String color; }
接口跟上面幾個同樣,調用下面的接口將拋出BindException
異常:
GET /student/color?color=CAY HTTP/1.1 Host: localhost:8888
當咱們把參數color
賦值爲BLUE
或者YELLOW
後,可以成功獲得響應。
在實際使用起來咱們會遇到一些問題,這裏總結了一些常見的問題和處理方式。
上面爲了校驗顏色咱們聲明瞭一個Param
對象來包裝惟一的字符串參數color
,爲何直接使用下面的方式定義呢?
@GetMapping("/color") public Rest<?> color(@Valid @Color({Colors.BLUE,Colors.YELLOW}) String color) { return RestBody.okData(color); }
或者使用路徑變量:
@GetMapping("/rest/{color}") public Rest<?> rest(@Valid @Color({Colors.BLUE, Colors.YELLOW}) @PathVariable String color) { return RestBody.okData(color); }
上面兩種方式是不會生效的。不信你能夠試一試,起碼在Spring Boot 2.3.1.RELEASE是不會直接生效的。
使以上兩種生效的方法是在類上添加@Validated
註解。注意必定要添加到方法所在的類上才行。這時候會拋出ConstraintViolationException
異常。
就像下面的寫法,方法的參數爲集合時,如何檢驗元素的約束呢?
/** * 集合類型參數元素. * * @param student the student * @return the rest */ @PostMapping("/batchadd") public Rest<?> batchAddStudent(@Valid @RequestBody List<Student> student) { return RestBody.okData(student); }
一樣是在類上添加@Validated
註解。注意必定要添加到方法所在的類上才行。這時候會拋出ConstraintViolationException
異常。
嵌套的結構如何校驗呢?打個比方,若是咱們在學生類Student
中添加了其所屬的學校信息School
並但願對School
的屬性進行校驗。
@Data public class Student { @NotBlank(message = "姓名必須填") private String name; @Age private Integer age; @NotEmpty(message = "成績必填") private List<Double> scores; @NotNull(message = "學校不能爲空") private School school; } @Data public class School { @NotBlank(message = "學校名稱不能爲空") private String name; @Min(value = 0,message ="校齡大於0" ) private Integer age; }
當 GET請求時正常校驗了School
的屬性,可是POST請求卻沒法對School
的屬性進行校驗。這時咱們只須要在該屬性上加上@Valid
註解便可。
@Data public class Student { @NotBlank(message = "姓名必須填") private String name; @Age private Integer age; @NotEmpty(message = "成績必填") private List<Double> scores; @Valid @NotNull(message = "學校不能爲空") private School school; }
每加一層嵌套都須要加一層@Valid
註解。一般在校驗對象屬性時,@NotNull
、@NotEmpty
和@Valid
配合才能起到校驗效果。
若是你有其它問題能夠經過felord.cn聯繫到我探討。
經過校驗框架咱們能夠專心於業務開發,本文對Hibernate Validator的使用和一些常見問題進行了梳理。咱們能夠經過Spring Boot統一異常處理來解決參數校驗的異常信息的提示問題。具體能夠經過關注:碼農小胖哥 回覆 valid獲取相關DEMO。
關注公衆號:Felordcn 獲取更多資訊