一、概要
validation主要是校驗用戶提交的數(shù)據(jù)的合法性,比如是否為空,密碼是否符合規(guī)則,郵箱格式是否正確等等,校驗框架比較多,用的比較多的是hibernate-validator, 也支持國際化,也可以自定義校驗類型的注解
官方文檔
二、基礎(chǔ)使用
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.*;
@Data
public class UserQuery {
private Long id;
@NotBlank
@Length(min = 6, max = 15)
private String username;
@NotBlank
@Email
private String email;
@NotBlank
@Pattern(regexp = "^(1[3-9][0-9])\\d{8}$", message = "手機號格式不正確")
private String phone;
@Min(value = 1)
@Max(value = 100)
private int age;
@NotBlank
@Length(min = 6, max = 12, message = "昵稱長度為6到12位")
private String nickname;
}
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
public class AccountController {
@PostMapping("/users")
public String register(@Valid @RequestBody UserQuery user, BindingResult result) {
if (result.hasErrors()) {
FieldError fieldError = result.getFieldError();
String field = fieldError.getField();
String msg = fieldError.getDefaultMessage();
return field + ":" + msg;
}
return "success";
}
}
三、常用驗證
1、JSR-330
validator內(nèi)置注解
| 注解 | 詳細信息 |
|---|---|
@Null |
被注釋的元素必須為 null
|
@NotNull |
被注釋的元素必須不為 null
|
@AssertTrue |
被注釋的元素必須為 true
|
@AssertFalse |
被注釋的元素必須為 false
|
@Min(value) |
被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 |
@Max(value) |
被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 |
@DecimalMin(value) |
被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 |
@DecimalMax(value) |
被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 |
@Size(max, min) |
被注釋的元素的大小必須在指定的范圍內(nèi) |
@Digits (integer, fraction) |
被注釋的元素必須是一個數(shù)字,其值必須在可接受的范圍內(nèi) |
@Past |
被注釋的元素必須是一個過去的日期 |
@Future |
被注釋的元素必須是一個將來的日期 |
@Pattern(value) |
被注釋的元素必須符合指定的正則表達式 |
2、Hibernate Validator 附加的 constraint
| 注解 | 詳細信息 |
|---|---|
@Email |
被注釋的元素必須是電子郵箱地址 |
@Length |
被注釋的字符串的大小必須在指定的范圍內(nèi) |
@NotEmpty |
被注釋的字符串的必須非空 |
@Range |
被注釋的元素必須在合適的范圍內(nèi) |
@NotBlank |
驗證字符串非null,且長度必須大于0 |
注意:
-
@NotNull適用于任何類型被注解的元素必須不能與NULL -
@NotEmpty適用于String Map或者數(shù)組不能為Null且長度必須大于0 -
@NotBlank只能用于String上面 不能為null,調(diào)用trim()后,長度必須大于0
四、校驗?zāi)J?/h2>
1、說明
- 普通模式(默認是這個模式)
- 快速失敗返回模式
2、普通模式
校驗完所有的屬性,然后返回所有的驗證失敗信息。默認就是普通模式無需配置
3、快速返回模式
只要有一個驗證失敗,則返回錯誤信息
@Configuration
public class ValidateConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
五、校驗方式
1、說明
常見的驗證方式:
- 封裝成Bean進行校驗
- 單個參數(shù)校驗
- 分組校驗
- 關(guān)聯(lián)校驗
2、栗子
2.1、請求參數(shù)封裝成實體類
@Data
@Validated
public class ProductQuery {
@NotNull
private String keyword;
@Min(value = 0, message = "參數(shù)不合法")
private Long categoryId;
private Integer page = 1;
private Integer size = 10;
private Integer sort = 0;
}
@RestController
public class ValidatedController {
@GetMapping("/validated")
public String validatedBean(@Valid ProductQuery userVo, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 直接獲取錯誤信息
List<String> errors = bindingResult.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
//獲取參數(shù)的屬性信息
// List<String> errors = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
}
return "sucess";
}
}
2.2、方法參數(shù)校驗
- 在類上使用@Validated注解
- 在參數(shù)上使用校驗注解即可
@RestController
@Validated
public class ValidatedController {
@GetMapping("/validated/params")
public String validatedParams(@RequestParam @NotNull Long id, BindingResult bindingResult) {
return bindingResult.hasErrors() ? "success" : "error";
}
}
2.3、關(guān)聯(lián)校驗
校驗對象內(nèi)部包含另一個校驗對象
@Data
@Validated
public class ProductQuery {
@NotNull
private String keyword;
@Min(value = 0, message = "參數(shù)不合法")
private Long categoryId;
private Integer page = 1;
private Integer size = 10;
private Integer sort = 0;
@Valid
BrandQuery brandQuery;
}
@Data
public class BrandQuery {
@NotBlank
private String brandName;
}
@GetMapping("/validated/association")
public String validatedAssociation(@Validated ProductQuery userVo, BindingResult bindingResult) {
return bindingResult.hasErrors() ? "success" : "error";
}
2.4、分組校驗
在實際開發(fā)中經(jīng)常會遇到這種情況:想要用一個實體類去接收多個controller的參數(shù),但是不同controller所需要的參數(shù)又有些許不同。
分組順序校驗時,按指定的分組先后順序進行驗證,前面的驗證不通過,后面的分組就不行驗證。
六、校驗錯誤處理
1、說明
- ConstraintViolationException
- MethodArgumentNotValidException
- BindException
2、栗子
2.1、方法參數(shù)校驗處理
校驗失敗會拋出ConstraintViolationException異常 然后我們在全局異常捕獲類捕獲這個異常,返回統(tǒng)一的結(jié)果集
@GetMapping("/validated/constraint")public ResponseResult<String> validatedError(@RequestParam @NotNull Long id) { return ResponseResult.success("success");}
@RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler { /** * ConstraintViolationException * 單個參數(shù)違反約束校驗異常 */ @ExceptionHandler(ConstraintViolationException.class) public ResponseResult<String> handleConstraintViolationException(ConstraintViolationException e) { log.error(e.getMessage(), e); return ResponseResult.error(); }}
2.2、請求參數(shù)表單對象
@Data@Validatedpublic class ProductQuery { @NotNull private String productId; @NotNull private String keyword; @Min(value = 0, message = "參數(shù)不合法") private Long categoryId; private Integer page = 1; private Integer size = 10; private Integer sort = 0; @Valid BrandQuery brandQuery;}
@PostMapping("/validate/method")public User methodArgs(@Valid @RequestBody ProductQuery productQuery){ return user;}
2.3、普通對象
也是不使用@RequestBody 會拋出BindException異常
@PostMapping("/validate/method")public ResponseResult<String> methodArgs(@Valid ProductQuery productQuery){ return ResponseResult.success("success");}
七、自定義校驗
1、說明
- 創(chuàng)建約束注解類
- 創(chuàng)建驗證器
2、創(chuàng)建注解類
@Target({ElementType.PARAMETER, ElementType.FIELD})@Documented@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = CustomOrderValidator.class)public @interface CustomOrder { String values(); String message() default "必須是規(guī)定字段"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}
3、創(chuàng)建校驗類
/** * 自定義排序字段校驗規(guī)則 */public class CustomOrderValidator implements ConstraintValidator<CustomOrder, String> { private String values; @Override public void initialize(CustomOrder constraintAnnotation) { this.values = constraintAnnotation.values(); } /** * 否則,校驗不通過。 * * @param value 用戶輸入的值,如從前端傳入的某個值 */ @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { String[] args = values.split(", "); if (args.length > 0) { for (String val : args) { if (val.equals(value)) { return true; } } } return false; }}
4、使用
@GetMapping("/search")public ResponseResult<String> search(@CustomOrder(values = "1,2,3,4") Integer sort) { return ResponseResult.success("success");}
- 普通模式(默認是這個模式)
- 快速失敗返回模式
校驗完所有的屬性,然后返回所有的驗證失敗信息。默認就是普通模式無需配置
只要有一個驗證失敗,則返回錯誤信息
@Configuration
public class ValidateConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
常見的驗證方式:
- 封裝成Bean進行校驗
- 單個參數(shù)校驗
- 分組校驗
- 關(guān)聯(lián)校驗
@Data
@Validated
public class ProductQuery {
@NotNull
private String keyword;
@Min(value = 0, message = "參數(shù)不合法")
private Long categoryId;
private Integer page = 1;
private Integer size = 10;
private Integer sort = 0;
}
@RestController
public class ValidatedController {
@GetMapping("/validated")
public String validatedBean(@Valid ProductQuery userVo, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// 直接獲取錯誤信息
List<String> errors = bindingResult.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
//獲取參數(shù)的屬性信息
// List<String> errors = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.toList());
}
return "sucess";
}
}
@RestController
@Validated
public class ValidatedController {
@GetMapping("/validated/params")
public String validatedParams(@RequestParam @NotNull Long id, BindingResult bindingResult) {
return bindingResult.hasErrors() ? "success" : "error";
}
}
校驗對象內(nèi)部包含另一個校驗對象
@Data
@Validated
public class ProductQuery {
@NotNull
private String keyword;
@Min(value = 0, message = "參數(shù)不合法")
private Long categoryId;
private Integer page = 1;
private Integer size = 10;
private Integer sort = 0;
@Valid
BrandQuery brandQuery;
}
@Data
public class BrandQuery {
@NotBlank
private String brandName;
}
@GetMapping("/validated/association")
public String validatedAssociation(@Validated ProductQuery userVo, BindingResult bindingResult) {
return bindingResult.hasErrors() ? "success" : "error";
}
在實際開發(fā)中經(jīng)常會遇到這種情況:想要用一個實體類去接收多個controller的參數(shù),但是不同controller所需要的參數(shù)又有些許不同。
分組順序校驗時,按指定的分組先后順序進行驗證,前面的驗證不通過,后面的分組就不行驗證。
- ConstraintViolationException
- MethodArgumentNotValidException
- BindException
校驗失敗會拋出ConstraintViolationException異常 然后我們在全局異常捕獲類捕獲這個異常,返回統(tǒng)一的結(jié)果集
@GetMapping("/validated/constraint")public ResponseResult<String> validatedError(@RequestParam @NotNull Long id) { return ResponseResult.success("success");}
@RestControllerAdvice@Slf4jpublic class GlobalExceptionHandler { /** * ConstraintViolationException * 單個參數(shù)違反約束校驗異常 */ @ExceptionHandler(ConstraintViolationException.class) public ResponseResult<String> handleConstraintViolationException(ConstraintViolationException e) { log.error(e.getMessage(), e); return ResponseResult.error(); }}
@Data@Validatedpublic class ProductQuery { @NotNull private String productId; @NotNull private String keyword; @Min(value = 0, message = "參數(shù)不合法") private Long categoryId; private Integer page = 1; private Integer size = 10; private Integer sort = 0; @Valid BrandQuery brandQuery;}
@PostMapping("/validate/method")public User methodArgs(@Valid @RequestBody ProductQuery productQuery){ return user;}
也是不使用@RequestBody 會拋出BindException異常
@PostMapping("/validate/method")public ResponseResult<String> methodArgs(@Valid ProductQuery productQuery){ return ResponseResult.success("success");}
@Target({ElementType.PARAMETER, ElementType.FIELD})@Documented@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = CustomOrderValidator.class)public @interface CustomOrder { String values(); String message() default "必須是規(guī)定字段"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}
/** * 自定義排序字段校驗規(guī)則 */public class CustomOrderValidator implements ConstraintValidator<CustomOrder, String> { private String values; @Override public void initialize(CustomOrder constraintAnnotation) { this.values = constraintAnnotation.values(); } /** * 否則,校驗不通過。 * * @param value 用戶輸入的值,如從前端傳入的某個值 */ @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { String[] args = values.split(", "); if (args.length > 0) { for (String val : args) { if (val.equals(value)) { return true; } } } return false; }}
@GetMapping("/search")public ResponseResult<String> search(@CustomOrder(values = "1,2,3,4") Integer sort) { return ResponseResult.success("success");}