目录
- 单参数校验
- 实体类校验
- 分组校验
- 嵌套校验
- 自定义注解
参数校验主要使用两个标签@Validated和@Valid;
@Valid是Hibernate的注解校验,@Validated是spring的,是@Valid的增强;这两个标签也有一些不同之处,@Valid可以标注在成员属性上也可以嵌套校验,而@Validated不行,但是@Validated可以使用分组校验;
maven导入:
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-validation</artifactId> | |
<version>.7.5</version> | |
</dependency> |
通常用到的注解基本都在javax.validation.constraints包下,基本都有value(设定值)、message(设置错误消息)、groups(指定分组)属性:
- @AssertFalse:必须为false,支持boolean和Boolean,null是有效的;
- @AssertTrue:必须为true,支持boolean和Boolean,null是有效的;
- @DecimalMax:必须是一个小于或者小于等于设定值的数字,可以用inclusive指定是否包含数字(默认true),并且value是String类型的,支持BigDecimal、BigInteger、CharSequence、byte / short / int / long以及它们的包装类(由于舍入原因不支持double和float),null是有效的;
- @DecimalMin:必须是一个大于或者大于等于设定值的数字,其他同@DecimalMax;
- @Digits:设定可接受范围内的数字,必须指定integer(整数位数)和fraction(小数位数),支持BigDecimal、BigInteger、CharSequence、byte / short / int / long以及它们的包装类,null是有效的;
- @Email:必须是一个正确格式的邮箱,可以使用regexp指定正则表达式(默认是任意字符串),可以使用flags指定正则表达式的选项,null是有效的;
- @Future:必须是一个未来的瞬间、日期或时间(Now是虚拟机默认的当前时区的时间),支持java.util.Date、java.util.Calendar、java.time.Instant、java.time.LocalDate、java.time.LocalDateTime、java.time.LocalTime、java.time.MonthDay、java.time.OffsetDateTime、java.time.OffsetTime、java.time.Year、java.time.YearMonth、java.time.ZonedDateTime、java.time.chrono.HijrahDate、java.time.chrono.JapaneseDate、java.time.chrono.MinguoDate、java.time.chrono.ThaiBuddhistDate,null是有效的;
- @FutureOrPresent:必须是现在或未来的瞬间、日期或时间,其他同@Future;
- @Max:必须是小于等于指定值的数字,value是long类型,支持BigDecimal、BigInteger、byte / short / int / long以及它们的包装类(不支持float和double),null是有效的;
- @Min:必须是大于等于指定值的数字,其他同@Max;
- @Negative:必须是一个严格的负数,0为无效值,支持BigDecimal、BigInteger、byte / short / int / long / float / double以及它们的包装类,null是有效的;
- @NegativeOrZero,必须是负数或者0,其他同@Negative;
- @NotBlank:不能为null,并且至少包含一个非空白字符,接受CharSequence;
- @NotEmpty:不能为null或空(集合),支持CharSequence(字符序列长度)、Collection(集合size)、Map(map size)、Array(数组长度);
- @NotNull:不能为null,支持所有类型;
- @Null:必须为null,支持所有类型;
- @Past:必须是一个过去的瞬间、日期或时间,其他同@Future;
- @PastOrPresent:必须是现在或过去的瞬间、日期或时间,其他同@Future;
- @Pattern:必须符合指定的正则表达式,必须使用regexp参数指定正则表达式;
- @Positive:必须是一个严格的正数,0为无效值,其他同@Negative;
- @PositiveOrZero:必须是正数或者0,其他同@Negative;
- @Size:指定元素大小必须在指定范围内(包括边界值),使用min指定下边界(默认0),使用max指定上边界(默认Integer.MAX_VALUE),支持CharSequence、Collection、Map、Array,null是有效的;
单参数校验
在controller类上添加@Validated标签,在方法的参数前加验证标签,并且同一个参数可以添加多个标签;
启动类:(使用默认配置,端口8080)
package testspringboot.testparamvalidation; | |
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.autoconfigure.SpringBootApplication; | |
public class TestMain { | |
/** | |
* @param args | |
*/ | |
public static void main(String[] args) { | |
SpringApplication.run(TestMain.class, args); | |
} | |
} |
controller类:
package testspringboot.testparamvalidation; | |
import java.util.List; | |
import java.util.stream.Collectors; | |
import javax.validation.constraints.Max; | |
import javax.validation.constraints.Min; | |
import javax.validation.constraints.NotNull; | |
import org.springframework.validation.BindingResult; | |
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; | |
public class TestController { | |
public String a( String s, long a) { | |
System.out.println(s); | |
System.out.println(a); | |
return String.format("s:%s a:%d", s, a); | |
} | |
} |
postman测试:
返回的错误msg也可以使用自定义设定,使用@RestControllerAdvice注释一个类,然后在类里定义各种错误msg,就像这样:
package testspringboot.testparamvalidation; | |
import java.util.List; | |
import java.util.stream.Collectors; | |
import javax.validation.ConstraintViolation; | |
import javax.validation.ConstraintViolationException; | |
import org.springframework.validation.ObjectError; | |
import org.springframework.web.bind.MethodArgumentNotValidException; | |
import org.springframework.web.bind.annotation.ExceptionHandler; | |
import org.springframework.web.bind.annotation.RestControllerAdvice; | |
public class ValidException { | |
public String handleValidException(MethodArgumentNotValidException e) { | |
List<String> msgList = e.getBindingResult().getAllErrors() | |
.stream() | |
.map(ObjectError::getDefaultMessage) | |
.collect(Collectors.toList()); | |
return "MethodArgumentNotValidException: " + msgList.toString(); | |
} | |
public String handleConstraintViolationException(ConstraintViolationException e) { | |
List<String> msgList = e.getConstraintViolations() | |
.stream() | |
.map(ConstraintViolation::getMessage) | |
.collect(Collectors.toList()); | |
return "ConstraintViolationException: " + msgList.toString();//返回错误描述 | |
} | |
} |
再次测试结果:
默认验证所有参数,即使前面验证不通过也会继续验证,可以设置快速失败,使验证失败立即返回不继续验证;
自定义注入Validator类:
package testspringboot.testparamvalidation; | |
import javax.validation.Validation; | |
import javax.validation.Validator; | |
import javax.validation.ValidatorFactory; | |
import org.hibernate.validator.HibernateValidator; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
public class ValidatConfig { | |
public Validator validator() { | |
ValidatorFactory vfactory = Validation.byProvider(HibernateValidator.class) | |
.configure() | |
.failFast(true)//开启快速失败 | |
.buildValidatorFactory(); | |
return vfactory.getValidator(); | |
} | |
} |
再次测试结果,只显示一个错误msg了:
实体类校验
只需要在controller类的方法实体类参数前加@Validated或者@Valid标签,实体类里的属性前加验证标签,controller类上可以不用@Validated标签也行;
实体类:
package testspringboot.testparamvalidation; | |
import javax.validation.constraints.Max; | |
import javax.validation.constraints.NotNull; | |
public class Bparam { | |
public String s; | |
"Bparam的x参数不能超过10") | (value =, message =|
public int x; | |
public String getS() { | |
return s; | |
} | |
public void setS(String s) { | |
this.s = s; | |
} | |
public int getX() { | |
return x; | |
} | |
public void setX(int x) { | |
this.x = x; | |
} | |
public String toString() { | |
return "Bparam [s=" + s + ", x=" + x + "]"; | |
} | |
} |
controller类里的方法:
@RequestMapping("/b") | |
public String b(@Valid Bparam b) { | |
return b.toString(); | |
} |
测试:
另外错误消息也可以在controller类的方法参数里接收,参数里使用BindingResult就可以处理:
"/b") | (|
public String b() { Bparam b, BindingResult result | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return b.toString(); | |
} |
使用post的消息体接收参数也一样,在参数前多加一个@RequestBody:
"/b") | (|
public String b() { Bparam b, BindingResult result | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return b.toString(); | |
} |
分组校验
可以为同一属性设置不同情况下应用不同的注解标签,需要在注解标签里使用groups参数,groups是一个class集合,一个标签可以设置多个group,在controller类里方法的参数前的@Validated标签里使用value指定要使用的group验证(可以指定多个group验证),没有设置groups的标签默认属于Default.class的group,设置group的class通常使用interface,可以写在外面或者直接写到实体类内部;
实体类:
package testspringboot.testparamvalidation; | |
import javax.validation.constraints.AssertFalse; | |
import javax.validation.constraints.AssertTrue; | |
import javax.validation.constraints.Min; | |
import javax.validation.constraints.NotBlank; | |
import javax.validation.constraints.Size; | |
public class Cparam { | |
@AssertTrue(message = "b应为true", groups = CparamBTrue.class) | |
@AssertFalse(message = "b应为false", groups = CparamBFalse.class) | |
public boolean b; | |
@NotBlank | |
@Size(min =, max = 5, message = "s的长度1~5") | |
public String s; | |
@Min() | |
public int i; | |
// interface CparamBTrue{} | |
// interface CparamBFalse{} | |
} | |
interface CparamBTrue{} | |
interface CparamBFalse{} |
controller方法:
"/c") | (|
public String c(class) Cparam c, BindingResult result) { (CparamBTrue. | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return "OK"; | |
} | |
"/c") | (|
public String c(class, Default.class}) Cparam c, BindingResult result) { (value = {CparamBFalse. | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return "OK"; | |
} |
分组测试:
c1只验证了group是CparamBTrue的成员b,c2除了验证了group是CparamBFalse的成员b,也验证了没有设置groups的s和i;
另外也可以设置动态组校验,根据某些条件和情况设置验证的groups,需要在实体类上添加@GroupSequenceProvider标签指定实现了DefaultGroupSequenceProvider接口并实现接口里getValidationGroups方法的class,getValidationGroups方法返回List<Class<?>>,即为当前请求需要使用的groups(返回值相当于controller类方法参数前@Validated标签里的value的作用);
例如根据实体类内boolean值指定int值使用正负数:
实体类:
package testspringboot.testparamvalidation; | |
import javax.validation.constraints.Negative; | |
import javax.validation.constraints.NotNull; | |
import javax.validation.constraints.Positive; | |
import org.hibernate.validator.group.GroupSequenceProvider; | |
public class Cparam { | |
public boolean b; | |
public int i; | |
public String toString() { | |
return "Cparam [b=" + b + ", i=" + i + "]"; | |
} | |
interface BTrue{} | |
interface BFalse{} | |
} |
GroupProvider:
package testspringboot.testparamvalidation; | |
import java.util.ArrayList; | |
import java.util.List; | |
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider; | |
public class CparamGroupProvider implements DefaultGroupSequenceProvider<C3param> { | |
public List<Class<?>> getValidationGroups(Cparam object) { | |
System.out.println("obj:" + object); | |
List<Class<?>> groupList = new ArrayList<>(); | |
groupList.add(Cparam.class);//实体类需要加入 | |
if (object != null) {//该方法会调用多次,object可能为null | |
//b为true时使用BTrue.class组,b为false时使用BFalse.class组 | |
groupList.add(object.b ? Cparam.BTrue.class : C3param.BFalse.class); | |
} | |
return groupList; | |
} | |
} |
controller方法:
public String c( C3param c3, BindingResult result) { | |
System.out.println("param:" + c); | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return "OK"; | |
} |
测试:
嵌套校验
实体类成员为另一个级联的类时,需要在成员前使用@Valid标签(支持嵌套),并且提供该级联的成员属性的get方法,另外级联类内部也需要提供需要验证的成员属性的get方法(不验证的成员不用get方法)
第一层实体类:
package testspringboot.testparamvalidation; | |
import javax.validation.Valid; | |
import javax.validation.constraints.AssertTrue; | |
public class Dparam { | |
public boolean b; | |
public Dparam d2; | |
public Dparam getD2() {//级联对象需要get方法 | |
return d; | |
} | |
} |
第二层实体类:
package testspringboot.testparamvalidation; | |
import javax.validation.Valid; | |
import javax.validation.constraints.Positive; | |
public class Dparam { | |
public int i; | |
public String s;//不验证,不需get方法 | |
public Dparam d3; | |
public int getI() {//成员需要get方法 | |
return i; | |
} | |
public Dparam getD3() {//级联对象需要get方法 | |
return d; | |
} | |
} |
第三层实体类:
package testspringboot.testparamvalidation; | |
import javax.validation.constraints.NotNull; | |
public class Dparam { | |
"Dparam.s不能为null") | (message =|
public String s; | |
public String getS() {//成员需要get方法 | |
return s; | |
} | |
} |
controller方法:
"/d") | (|
public String d() { Dparam d1, BindingResult result | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return "OK"; | |
} |
测试:
自定义注解
定义一个注解,使用@Retention、@Target、@Constraint标签注释,并携带三个方法message()、groups()、payload(),并在@Constraint标签里使用validatedBy属性指定自定义验证类,自定义验证类实现ConstraintValidator<A extends Annotation, T>接口的boolean isValid(T value, ConstraintValidatorContext context)方法,判断验证是否通过;
自定义注解:(功能:验证是偶数)
package testspringboot.testparamvalidation; | |
import static java.lang.annotation.ElementType.FIELD; | |
import static java.lang.annotation.RetentionPolicy.RUNTIME; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.Target; | |
import javax.validation.Constraint; | |
import javax.validation.Payload; | |
public EAnnotation { | |
String message() default "应该是偶数"; | |
Class<?>[] groups() default {}; | |
Class<? extends Payload>[] payload() default {}; | |
} |
对应的验证类:
package testspringboot.testparamvalidation; | |
import javax.validation.ConstraintValidator; | |
import javax.validation.ConstraintValidatorContext; | |
public class EValidator implements ConstraintValidator<EAnnotation, Integer> { | |
public boolean isValid(Integer value, ConstraintValidatorContext context) { | |
return value % == 0; | |
} | |
} |
实体类:
package testspringboot.testparamvalidation; | |
public class Eparam { | |
public int i; | |
} |
controller方法:
"/e") | (|
public String e() { Eparam e, BindingResult result | |
if (result.hasErrors()) { | |
List<String> errors = result.getAllErrors().stream().map(x -> x.getDefaultMessage()).collect(Collectors.toList()); | |
return "BindingResult Errors: " + errors.toString(); | |
} | |
return "OK"; | |
} |
测试: