title: SpringBoot校驗(yàn)(validation)
date: 2019-07-15
author: maxzhao
tags:
- SpringBoot
- validation
- hibernate-validation
categories:
- SpringBoot
元編程
一個(gè)健壯的系統(tǒng)都要對(duì)外部提交的數(shù)據(jù)進(jìn)行完整性、合法性的校驗(yàn)。
校驗(yàn)是我們程序開發(fā)中必不可少的過程。
即使開發(fā)一個(gè)不面對(duì)最終用戶的工具包,也需要對(duì)傳入的數(shù)據(jù)進(jìn)行縝密的校驗(yàn)來防止引發(fā)底層難以追蹤的問題。
后端參數(shù)校驗(yàn)最簡(jiǎn)單的做法是直接在業(yè)務(wù)方法里面進(jìn)行判斷,當(dāng)判斷成功之后再繼續(xù)往下執(zhí)行。但這樣帶給我們的是代碼的耦合,冗余。當(dāng)我們多個(gè)地方需要校驗(yàn)時(shí),我們就需要在每一個(gè)地方調(diào)用校驗(yàn)程序,導(dǎo)致代碼很冗余,且不美觀。
不使用Bean Validation校驗(yàn)數(shù)據(jù)的代碼基本都是靠大量的 if-else.所以我這里學(xué)習(xí)使用了 注解方式實(shí)現(xiàn)數(shù)據(jù)校驗(yàn).
使用
SpringBoot 中的 bean validation 是集成了hibernate-validator和tomcat-embed-el
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
簡(jiǎn)單的校驗(yàn)
@Valid:常見用在方法,類中字段上進(jìn)行校驗(yàn)
@Validated:是spring提供的對(duì)@Valid的封裝,常見用在方法上進(jìn)行校驗(yàn)
BindingResult:是驗(yàn)證是否錯(cuò)誤
Model中
@Range(max = 150, min = 1, message = "年齡范圍應(yīng)該在1-150內(nèi)。")
private Integer age;
Controller中
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
綁定多個(gè)校驗(yàn)對(duì)象
@PostMapping("save")
public void v1(@RequestBody @Valid AppUser appUser,BindingResult result,@RequestBody @Valid AppUser appUser2,BindingResult result2){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
部分標(biāo)簽
Bean Validation 中內(nèi)置的 constraint
| 注解 | 作用 |
|---|---|
| @Valid | 被注釋的元素是一個(gè)對(duì)象,需要檢查此對(duì)象的所有字段值 |
| @Null | 被注釋的元素必須為 null |
| @NotNull | 被注釋的元素必須不為 null |
| @AssertTrue | 被注釋的元素必須為 true |
| @AssertFalse | 被注釋的元素必須為 false |
| @Min(value) | 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 |
| @Max(value) | 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 |
| @DecimalMin(value) | 被注釋的元素必須是一個(gè)數(shù)字,其值必須大于等于指定的最小值 |
| @DecimalMax(value) | 被注釋的元素必須是一個(gè)數(shù)字,其值必須小于等于指定的最大值 |
| @Size(max, min) | 被注釋的元素的大小必須在指定的范圍內(nèi) |
| @Digits (integer, fraction) | 被注釋的元素必須是一個(gè)數(shù)字,其值必須在可接受的范圍內(nèi) |
| @Past | 被注釋的元素必須是一個(gè)過去的日期 |
| @Future | 被注釋的元素必須是一個(gè)將來的日期 |
| @Pattern(value) | 被注釋的元素必須符合指定的正則表達(dá)式 |
Hibernate Validator 附加的 constraint
| 注解 | 作用 |
|---|---|
| 被注釋的元素必須是電子郵箱地址 | |
| @Length(min=, max=) | 被注釋的字符串的大小必須在指定的范圍內(nèi) |
| @NotEmpty | 被注釋的字符串的必須非空 |
| @Range(min=, max=) | 被注釋的元素必須在合適的范圍內(nèi) |
| @NotBlank | 被注釋的字符串的必須非空 |
| @URL(protocol=,host=, port=,regexp=, flags=) | 被注釋的字符串必須是一個(gè)有效的url |
容易記錯(cuò)的
@NotNull 任何對(duì)象的value不能為null
@NotEmpty 集合對(duì)象的元素不為0,即集合不為空,也可以用于字符串不為null
@NotBlank 只能用于字符串不為null,并且字符串trim()以后length要大于0
resource 下新建錯(cuò)誤信息配置文件
在resource 目錄下新建提示信息配置文件 ValidationMessages.properties
文件中的格式為message=ASCII.
中文的ascii碼
hibernate的校驗(yàn)?zāi)J?/h2>
1、普通模式(默認(rèn)是這個(gè)模式)
普通模式(會(huì)校驗(yàn)完所有的屬性,然后返回所有的驗(yàn)證失敗信息)
2、快速失敗返回模式
快速失敗返回模式(只要有一個(gè)驗(yàn)證失敗,則返回)
兩種驗(yàn)證模式配置方式
(參考官方文檔)
failFast:true 快速失敗返回模式 false 普通模式
@Configuration public class ValidatorConfiguration { @Bean public Validator validator(){ ValidatorFactory validatorFactory = Validation .byProvider( HibernateValidator.class ) .configure() .failFast( true ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); } }
和 (hibernate.validator.fail_fast:true 快速失敗返回模式 false 普通模式)
@Configuration public class ValidatorConfiguration { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() .addProperty("hibernate.validator.fail_fast", "true") .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; } }
手動(dòng)驗(yàn)證Model
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}
分組校驗(yàn)(區(qū)分新增和修改的規(guī)則)
簡(jiǎn)單使用
場(chǎng)景
新增用戶信息和修改用戶信息所需要驗(yàn)證的字段是不同的.
public interface AppUserVaildC {
}
public interface AppUserVaildU {
}
Model中
Default 是默認(rèn)分組.
@Range(min = 0,max = 100,message = "年齡必須在[0,100]",groups={Default.class})
/**年齡*/
private Integer age;
@Range(min = 0,max = 2,message = "性別必須在[0,2]",groups = {AppUserVaildC.class})
/**性別 0:未知;1:男;2:女*/
private Integer sex;
Controller中使用
@PostMapping("save")
public void v1(@RequestBody @Validated({AppUserVaildC.class, AppUserVaildU.class}) AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
普通使用
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,AppUserVaildC,AppUserVaildU);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}
組序列
除了按組指定是否驗(yàn)證之外,還可以指定組的驗(yàn)證順序,前面組驗(yàn)證不通過的,后面組不進(jìn)行驗(yàn)證
@GroupSequence({AppUserVaildC.class, AppUserVaildU.class, Default.class})
public interface GroupOrder {
}
Controller中使用
@PostMapping("save")
public void v1(@RequestBody @Validated({GroupOrder.class}) AppUser appUser,BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
}
普通使用
AppUser appUser=new AppUser();
Set<ConstraintViolation<AppUser>> violationSet = validator.validate(appUser,GroupOrder);
for (ConstraintViolation<AppUser> model : violationSet) {
System.out.println(model.getMessage());
}
自定義驗(yàn)證
下面是一個(gè)自定義大小寫的驗(yàn)證
public enum CaseMode {
UPPER,
LOWER;
}
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
CaseMode value();
}
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
public void initialize(CheckCase checkCase) {
this.caseMode = checkCase.value();
}
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (s == null) {
return true;
}
if (caseMode == CaseMode.UPPER) {
return s.equals(s.toUpperCase());
} else {
return s.equals(s.toLowerCase());
}
}
}
Model中
@Range(value = CaseMode.LOWER ,message = "年必須是小寫",groups={Default.class})
/**年齡*/
private String loginName;
@Range(min = 0,max = 2,message = "性別必須在[0,2]",groups = {AppUserVaildC.class})
/**性別 0:未知;1:男;2:女*/
private Integer sex;
validator 配置
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
本文地址: