----------------------------------------------------------------------------------------
在实际的项目开发中,经常会遇到对参数进行校验的场景,最常见的就是后端需要对前端传过来的数据进行校验。
我理解的数据校验大致分为两类:
一类是对数据本身进行校验,不涉及与数据库交互的,比如正则校验、非空校验、指定的枚举数据、最大值、最小值等等。
二类是数据的校验需要和数据库交互的,比如是否唯一(数据库中是否存在)、数量限制(数据库中只能允许存在10条数据)等等。
由于第二类其实属于业务逻辑,这里不做讨论,本文主要是针对第一类场景的数据校验。
其实也可以在业务代码中去做校验判断,但是这样就不够优雅了不是吗,话不多说直接开始正文
1、@Valid和@Validated介绍以及对应的Maven坐标
4、@Valid的嵌套校验(校验的对象中引入的其他对象或者List对象的校验)
5、@Validated的分组校验(不同的分组不同的校验策略)
6、@Validated中的分组校验时@GroupSequence使用(指定字段的校验顺序)
7、快速失败机制(单个参数校验失败后,不再对剩下的参数进行校验)
1、@Valid和@Validated介绍以及对应的Maven坐标(回到目录)
@Valid和@Validated主要是用于表单校验
Maven一般是跟随spring-boot-starter-parent,也可以自行选择对应的版本,目前spring-boot-starter-validation最新的版本是2.7.0,Maven中心
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.3.7.RELEASE</version>
</dependency>
2、@Valid和@Validated中常用的注解(回到目录)
常用的注解如下图,可能由于版本不同略有出入,具体的含义可以看注解上的注释,下面提供了一份整理的注解含义
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
@Future 限制必须是一个将来的日期
@FutureOrPresent 未来或当前的日期,此处的present概念是相对于使用约束的类型定义的。例如校验的参数为Year year = Year.now();此时约束是一年,那么“当前”将表示当前的整个年份。
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Negative 绝对的负数,不能包含零,空元素有效可以校验通过
@NegativeOrZero 包含负数和零,空元素有效可以校验通过
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotNull 限制必须不为null
@Null 限制只能为null
@Past 限制必须是一个过去的日期
@PastOrPresent 过去或者当前时间,和@FutureOrPresent类似
@Pattern(value) 限制必须符合指定的正则表达式
@Positive 绝对的正数,不能包含零,空元素有效可以校验通过
@PositiveOrZero 包含正数和零,空元素有效可以校验通过
@Size(max,min) 限制字符长度必须在min到max之间
3、@Valid和@Validated区别和对应使用场景(回到目录)
@Valid可以实现嵌套校验,对于对象中引用了其他的对象,依然可以校验
@Validated可以对参数校验进行分组,例如一个对象里面有一个字段id,id在新增数据时可以为空,但是在更新数据时不能为空,此时就需要用到校验分组
具体的使用见下面的章节
为了方便理解和构造使用场景,目前假设存在三个实体对象,分别是ProjectDTO(项目)、TeamDTO(团队)和MemberDTO(成员),彼此的关系是,一个项目中存在一个团队,一个团队中存在多个成员,实体类里面的属性虚构,目的是为了举例校验的相关注解。
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProjectDTO {
@NotBlank(message = "ID不能为空", groups = {TestValidGroup.Update.class})
private String id;
@NotBlank
@Pattern(regexp = "[a-zA-Z0-9]", message = "只允许输入数字和字母")
private String strValue;
@Min(value = -99, message = "值不能小于-99")
@Max(value = 100, message = "值不能超过100")
private Integer intValue;
@Negative(message = "值必须为负数")
private Integer negativeValue;
@EnumValue(strValues = {"agree", "refuse"})
private String strEnum;
@EnumValue(intValues = {1983, 1990, 2022})
private Integer intEnum;
@Valid
private TeamDTO teamDTO;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TeamDTO {
@FutureOrPresent(message = "只能输入当前年份或未来的年份")
private Year nowYear;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@Future(message = "只能是未来的时间")
private Date futureTime;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@Past(message = "只能是过去的时间")
private Date pastTime;
@Email(message = "请输入正确的邮箱")
private String email;
@Valid
private List<MemberDTO> list;
}
MemberDTO(成员)实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class MemberDTO {
@NotBlank(message = "姓名不能为空")
private String name;
@EnumValue(intValues = {0, 1, 2}, message = "性别值非法,0:男,1:女,2:其他")
private Integer sex;
}
4、@Valid的嵌套校验(校验的对象中引入的其他对象或者List对象的校验)(回到目录)
userInfo方法则是采用@Valid方式进行校验,传入的对象时TeamDTO(团队),实体类里面具体的参数可以看第3部分里面实体类的具体代码TeamDTO(团队)实体类
@RestController
@RequestMapping("/valid")
public class TestValidController {
@PostMapping("/userInfo")
public BaseResponse userInfo(@Valid @RequestBody TeamDTO teamDTO) {
return new BaseResponse(teamDTO);
}
}
关键点在于TeamDTO里面的属性List<MemberDTO> list,上面加上@Valid注解,如下:
@Valid
private List<MemberDTO> list;
postman测试结果如下:
可以看到list里面,MemberDTO也被校验了,name和sex不合法。
5、@Validated的分组校验(不同的分组不同的校验策略)(回到目录)
例如有一个场景,更新项目信息,项目id是必须要传的,但是在新增项目时,id可以不传,新增和更新用的同一个实体对象,这个时候需要根据不同的分组区分,不同的分组采用不同的校验策略,具体查询ProjectDTO(项目)实体类
@NotBlank(message = "ID不能为空", groups = {TestValidGroup.Update.class})
private String id;
如上,注解参数中存在一个groups,表示将该参数归为update组,可以指定一个参数属于多个组
Controller的代码如下,@Validated有一个参数值value,可以校验指定分组的属性,下面就是指定校验groups包含TestValidGroup.Update.class的属性,在ProjectDTO中只有id这个属性的groups满足条件,所以只会校验id这个参数。
@RestController
@RequestMapping("/valid")
public class TestValidController {
@PostMapping("/post")
public BaseResponse testValidPostRequest(@Validated(value = {TestValidGroup.Update.class}) @RequestBody ProjectDTO testAnnotationDto) {
return new BaseResponse(testAnnotationDto);
}
}
group如何自定义,其实很简单,就是自己定义一个接口,这个接口的作用只是用来分组,自己创建一个接口,代码如下:
分别表示在新增和更新两种情况,可以按实际需求在内部添加多个接口
public interface TestValidGroup {
interface Insert {
}
interface Update {
}
}
注意:未显示指定groups的字段,默认归于javax.validation.groups包下的Default.class(默认组)
@Validated的value不指定组时,只校验Default组的字段
@Validated的value指定组时,只校验属于指定组的字段,属于Default组的字段不会被校验
若想指定组和默认组都被校验,有两种方式:
1、在@Validated的value中加入默认组,如下:
@PostMapping("/post")
public BaseResponse testValidPostRequest(@Validated(value = {TestValidGroup.Update.class, Default.class}) @RequestBody ProjectDTO testAnnotationDto) {
return new BaseResponse(testAnnotationDto);
}
2、将指定的Update接口继承Default接口,如下:
public interface TestValidGroup {
interface Insert {
}
interface Update extends Default {
}
}
6、@Validated中的分组校验时@GroupSequence使用(指定字段的校验顺序)(回到目录)
从上面的Swagger调试截图可以知道,返回的是所有字段的校验结果,所以存在一个问题,那就是多个校验字段之间的顺序如何保证,如果不指定顺序,那么每次校验的顺序就会不同,那个错误提示信息也就不同,一些特殊场景会要求固定错误顺序,例如自动化测试脚本,每次都需要将返回的校验结果和预期结果比较,返回的校验结果一直变化就会有问题。
Controller层代码如下:
@RestController
@RequestMapping("/valid")
public class TestValidController {
@PostMapping("/post")
public BaseResponse testValidPostRequest(@Validated(value = {TestValidGroup.Update.class}) @RequestBody ProjectDTO testAnnotationDto) {
return new BaseResponse(testAnnotationDto);
}
}
指定校验顺序就会用到@GroupSequence注解,这个注解使用在group的接口上,可以针对每一个参数都进行分组,然后通过该注解去指定顺序,代码如下,例如update时,校验的顺序就是先校验group属于Id.class的字段,再校验group属于StrValue的字段。
public interface TestValidGroup {
@GroupSequence(value = {StrValue.class})
interface Insert {
}
@GroupSequence(value = {Id.class, StrValue.class})
interface Update {
}
interface Id {
}
interface StrValue {
}
}
注意:此时不是校验group属于Update.class的字段,而是校验 group属于@GroupSequence的value中的那些接口(Id.class, StrValue.class) 的字段,如下:
正确用法:
@NotBlank(message = "ID不能为空", groups = {TestValidGroup.Id.class})
private String id;
错误用法:
@NotBlank(message = "ID不能为空", groups = {TestValidGroup.Update.class})
private String id;
小知识:一个字段上存在多个注解时,例如@Max和@NotBlank,是按注解从上至下的顺序进行校验的。
7、快速失败机制(单个参数校验失败后,立马抛出异常,不再对剩下的参数进行校验)(回到目录)
实际情况中,有时候并不需要校验完所有的参数,只要校验失败,立马抛出异常,Validation提供了快速失败的机制,代码如下:
@Configuration
public class ValidConfig {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
8、自定义校验注解,实现特殊的校验逻辑(回到目录)
有时候会存在一些特殊的校验逻辑,已有的注解并不能满足要求,此时就可以自定义校验注解,自己实现特殊的校验逻辑,一般分为两步,1、自定义一个注解。2、实现该注解的校验逻辑
例子场景:目前想实现一种校验,传入的字符串必须在指定的字符串数组中存在,传入的数字必须在指定的Integer数组中存在,类似于枚举值。
1、自定义一个注解
自定义注解的方式不用多说,主要讲下和校验相关的地方,@Constraint(validatedBy = {EnumValueValidated.class}),这个注解很关键,里面的validatedBy = {EnumValueValidated.class}是指定具体的校验类,
具体的校验逻辑在EnumValueValidated类里面实现。另外就是注解里面的一些属性,例如message、groups、payload和内部的一个@List注解(这个注解的使用场景后面会讲到),这里可以参考validation已有的注解,基本都是很有用的。
然后就是定义自己需要的一些特殊的属性,方便校验,例如下面的注解中就包含了,isRequire、strValues、intValues。
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Constraint(validatedBy = {EnumValueValidated.class})
public @interface EnumValue {
/**
* 是否需要(true:不能为空,false:可以为空)
*/
boolean isRequire() default false;
/**
* 字符串数组
*/
String[] strValues() default {};
/**
* int数组
*/
int[] intValues() default {};
/**
* 枚举类
*/
Class<?>[] enumClass() default {};
String message() default "所传参数不在允许的值范围内";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface List {
EnumValue[] value();
}
}
2、实现该注解的校验逻辑
具体的代码如下,implements ConstraintValidator<EnumValue, Object>,实现两个方法,分别是initialize(初始化方法)和isValid(校验方法),initialize()主要是加载读取注解上的值并赋值给类变量,
isValid()是实现具体的校验逻辑,此处不具体说明,可自行实现。
public class EnumValueValidated implements ConstraintValidator<EnumValue, Object> {
private boolean isRequire;
private Set<String> strValues;
private List<Integer> intValues;
@Override
public void initialize(EnumValue constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
strValues = Sets.newHashSet(constraintAnnotation.strValues());
intValues = Arrays.stream(constraintAnnotation.intValues()).boxed().collect(Collectors.toList());
isRequire = constraintAnnotation.isRequire();
//将枚举类的name转小写存入strValues里面,作为校验参数
Optional.ofNullable(constraintAnnotation.enumClass()).ifPresent(e -> Arrays.stream(e).forEach(
c -> Arrays.stream(c.getEnumConstants()).forEach(v -> strValues.add(v.toString().toLowerCase()))
));
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null && !isRequire) {
return true;
}
if (value instanceof String) {
return strValues.contains(value);
}
if (value instanceof Integer) {
return intValues.stream().anyMatch(e -> e.equals(value));
}
return false;
}
}
9、全局异常处理,统一返回校验异常信息(回到目录)
项目中一般会针对异常进行统一处理,valid校验失败的异常是MethodArgumentNotValidException,所以可以拦截此类异常,进行异常信息的处理,捕获后的具体逻辑,自行实现,例子代码如下:
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerConfig {
/**
* 拦截valid参数校验返回的异常,并转化成基本的返回样式
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public BaseResponse dealMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("this is controller MethodArgumentNotValidException,param valid failed", e);
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
String message = allErrors.stream().map(s -> s.getDefaultMessage()).collect(Collectors.joining(";"));
return BaseResponse.builder().code("-10").msg(message).build();
}
}
10、@Interface List的使用场景(补充)(回到目录)
有时候会出现这种需求,同一个字段在不同的场景下,需要采用不同的校验规则,并返回不同的异常信息,目前有两种方式,一种是采用@List的方式,一种是在字段上重复使用同一个注解,具体代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
public class BaseDTO {
@NotBlank.List({
@NotBlank(message = "项目BaseId不能为空", groups = {TestValidGroup.Project.class}),
@NotBlank(message = "团队BaseId不能为空", groups = {TestValidGroup.Team.class})
})
private String baseId;
@Max(value = 10, message = "项目BaseId不能大于10", groups = {TestValidGroup.Project.class})
@Max(value = 30, message = "团队BaseId不能大于30", groups = {TestValidGroup.Team.class})
private Integer number;
}
目的是通过指定注解归属于不同的分组来到达区分的效果。
Controller代码如下:
@RestController
@RequestMapping("/valid")
public class TestValidController {
@PostMapping("/projectList")
public BaseResponse projectList(@Validated(value = {TestValidGroup.Project.class}) @RequestBody BaseDTO baseDTO) {
return new BaseResponse(baseDTO);
}
@PostMapping("/teamList")
public BaseResponse projectTeam(@Validated(value = {TestValidGroup.Team.class}) @RequestBody BaseDTO baseDTO) {
return new BaseResponse(baseDTO);
}
}
11、@Valid和@Validated组合使用(补充)(回到目录)
@Validated和Valid肯定是可以组合使用的,一种是分组,一种是嵌套,单独使用的注意点已经在上面的部分写过,下面简单描述下在Controller代码中的使用,其实很简单,就是在实体类(ProjectDTO)上同时加上这两个注解,代码如下:
@RestController
@RequestMapping("/valid")
public class TestValidController {
@PostMapping("/post")
public BaseResponse testValidPostRequest(@Valid @Validated(value = {TestValidGroup.Update.class, Default.class}) @RequestBody ProjectDTO testAnnotationDto) {
return new BaseResponse(testAnnotationDto);
}
}
----------------------------------------------------------------------------------------
@Validated注解
@Validated注解是为了给请求接口时,判断对象的值是否是你需要的属性做判断。
在编程中我使用的主要用的有:
1、@NotNull (不能为null)
2、@NotEmpty (不为空也不能为null,其主要限制String类型)
3、@NotBlank (不能为空,去除一开始的空格后长度不能为0)
在网上还看到一些常用注释,现记录下来:
感觉比较实用的:
1、@Size(max,min) (限制字符长度必须在min到max之前)
2、@Past (限制必须是过去的日期)
3、@PastOrPresent (限制必须是过去的日期或者是当前时间)
4、@Future (限制必须是以后的日期)
5、@FutureOrPresent (限制必须是以后的日期或者是当前时间)
6、@Max(value) (限制必须为不大于value的值)
7、@Min(value) (限制必须为不小于value的值)
8、@Pattern(value) (限制必须符合指定的正则表达式)
9、@Email (限制必须为email格式)
不常用的:
1、@Null (限制只能为空)
2、@AssertFalse (限制必须是false)
3、@AssertTrue. (限制必须为true)
4、@DecimalMax(value) (限制必须为不大于value的值)
5、@DecimalMin(value) (限制必须为不小于value的值)
6、@Digits(Integer,fraction)(限制必须为一个小数,且整数部分位数不超过Intger,小数不超过fraction)
7、@Negative (限制必须为负整数)
8、@NegativeOrZero (限制必须为负整数或零)
8、@Positive (限制必须为正整数)
9、@PositiveOrZero (限制必须为正整数或零)
现在来讲一下如何使用:
1、导入依赖(有两种)
第一种:(我使用的是第一种)
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
第二种:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2、创建对象、接口;
创建对象
@Data
public class XxxAddParam{
@NotNull(message = "用户id不能为空")
private Integer id;
@NotEmpty(message = "用户姓名不能为空")
private String name;
}
创建接口
@Slf4j
@RestController
@RequestMapping("/xxx")
public class XxxController {
@PostMapping("/addXxx")
public Result addXxx(@RequestBody @Validated XxxAddParam XxxAddParam){
return Result.success();
}
}
在一开始可能会遇到如下问题:
1、@Validated注解的默认异常过长;
org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.xwdBoy.common.helper.Result com.xwdBoy.web.controller.XxxController.addXxx(com.xwdBoy.param.XxxAddParam): [Field error in object 'XxxAddParam' on field 'name': rejected value []; codes [NotEmpty.XxxAddParam.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [XxxAddParam.name,name]; arguments []; default message [name]]; default message [用户姓名不能为空]]
2、无法对List<xx>对象进行验证;
第一个问题是需要加上全局异常处理就可以通过:
/**
* HTTP接口统一的错误处
* @author sj
*/
@ControllerAdvice
@Slf4j
@Priority(1)
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
List<FieldError> errors = bindingResult.getFieldErrors();
//初始化错误信息大小
Result result = new Result();
for (FieldError error : errors) {
result.setMsg(error.getDefaultMessage());
result.setCode(ResultEnum.ERROR.getCode());
return result;
}
return Result.error(ResultEnum.ERROR.getCode(), ResultEnum.ERROR.getMsg());
}
@ExceptionHandler(BizException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Result handleBizExceptions(BizException e) {
log.error(e.getMessage(), e);
return Result.error(e.getCode(), e.getMessage());
}
@ResponseBody
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result httpRequestMethodNotSupportedExceptionException(HttpRequestMethodNotSupportedException e) {
String message = Optional.ofNullable(e.getMessage()).orElse(ResultEnum.HTTP_REQUEST_METHOD_NOT_SUPPORTED.getMsg());
log.warn("HttpRequestMethodNotSupportedException:", e);
return Result.error(ResultEnum.HTTP_REQUEST_METHOD_NOT_SUPPORTED.getCode(), message);
}
@ResponseBody
@ExceptionHandler(MultipartException.class)
public Result fileUploadExceptionHandler(MultipartException e) {
log.warn("上传文件异常:{}", e.getMessage());
return Result.error(ResultEnum.ERROR.getCode(), "文件过大,单个不超200M");
}
}
第二个问题对list进行操作(在入参的时候使用ValidList<xx>):
@Data
public class ValidList<E> implements List<E> {
@Valid
private List<E> list = new LinkedList<>();
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Iterator<E> iterator() {
return list.iterator();
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
return list.add(e);
}
@Override
public boolean remove(Object o) {
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return list.retainAll(c);
}
@Override
public void clear() {
list.clear();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
return list.set(index, element);
}
@Override
public void add(int index, E element) {
list.add(index, element);
}
@Override
public E remove(int index) {
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
@Override
public ListIterator<E> listIterator() {
return list.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return list.listIterator(index);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
return list.subList(fromIndex, toIndex);
}
}
----------------------------------------------------------------------------------------
1.概述
本文我们将重点介绍Spring中 @Valid和@Validated注解的区别 。
验证用户输入是否正确是我们应用程序中的常见功能。Spring提供了@Valid
和@Validated
两个注解来实现验证功能,下面我们来详细介绍它们。
2. @Valid和@Validate注解
在Spring中,我们使用@Valid
注解进行方法级别验证,同时还能用它来标记成员属性以进行验证。
但是,此注释不支持分组验证。@Validated
则支持分组验证。
3.例子
让我们考虑一个使用Spring Boot开发的简单用户注册表单。首先,我们只有名称
和密码
属性:
public class UserAccount {
@NotNull
@Size(min = 4, max = 15)
private String password;
@NotBlank
private String name;
// standard constructors / setters / getters / toString
}
接下来,让我们看一下控制器。在这里,我们将使用带有@Valid
批注的saveBasicInfo
方法来验证用户输入:
@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
@Valid @ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result,
ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}
现在让我们测试一下这个方法:
@Test
public void givenSaveBasicInfo_whenCorrectInput`thenSuccess() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo")
.accept(MediaType.TEXT_HTML)
.param("name", "test123")
.param("password", "pass"))
.andExpect(view().name("success"))
.andExpect(status().isOk())
.andDo(print());
}
在确认测试成功运行之后,现在让我们扩展功能。下一步的逻辑步骤是将其转换为多步骤注册表格,就像大多数向导一样。第一步,名称
和密码
保持不变。在第二步中,我们将获取其他信息,例如age
和 phone
。因此,我们将使用以下其他字段更新域对象:
public class UserAccount {
@NotNull
@Size(min = 4, max = 15)
private String password;
@NotBlank
private String name;
@Min(value = 18, message = "Age should not be less than 18")
private int age;
@NotBlank
private String phone;
// standard constructors / setters / getters / toString
}
但是,这一次,我们将注意到先前的测试失败。这是因为我们没有传递年龄
和电话
字段。
为了支持此行为,我们引入支持分组验证的@Validated
批注。
分组验证
,就是将字段分组,分别验证,比如我们将用户信息分为两组:BasicInfo
和AdvanceInfo
可以建立两个空接口:
public interface BasicInfo {
}
public interface AdvanceInfo {
}
第一步将具有BasicInfo
接口,第二步 将具有AdvanceInfo
。此外,我们将更新UserAccount
类以使用这些标记接口,如下所示:
public class UserAccount {
@NotNull(groups = BasicInfo.class)
@Size(min = 4, max = 15, groups = BasicInfo.class)
private String password;
@NotBlank(groups = BasicInfo.class)
private String name;
@Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class)
private int age;
@NotBlank(groups = AdvanceInfo.class)
private String phone;
// standard constructors / setters / getters / toString
}
另外,我们现在将更新控制器以使用@Validated
注释而不是@Valid
:
@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)
public String saveBasicInfoStep1(
@Validated(BasicInfo.class)
@ModelAttribute("useraccount") UserAccount useraccount,
BindingResult result, ModelMap model) {
if (result.hasErrors()) {
return "error";
}
return "success";
}
更新后,再次执行测试,现在可以成功运行。现在,我们还要测试这个新方法:
@Test
public void givenSaveBasicInfoStep1`whenCorrectInput`thenSuccess() throws Exception {
this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1")
.accept(MediaType.TEXT_HTML)
.param("name", "test123")
.param("password", "pass"))
.andExpect(view().name("success"))
.andExpect(status().isOk())
.andDo(print());
}
也成功运行!
接下来,让我们看看@Valid
对于触发嵌套属性验证是必不可少的。
4.使用@Valid
批注标记嵌套对象
@Valid 可以用于嵌套对象。例如,在我们当前的场景中,让我们创建一个 UserAddress
对象:
public class UserAddress {
@NotBlank
private String countryCode;
// standard constructors / setters / getters / toString
}
为了确保验证此嵌套对象,我们将使用@Valid
批注装饰属性:
public class UserAccount {
//...
@Valid
@NotNull(groups = AdvanceInfo.class)
private UserAddress useraddress;
// standard constructors / setters / getters / toString
}
5. 总结
@Valid
保证了整个对象的验证, 但是它是对整个对象进行验证,当仅需要部分验证的时候就会出现问题。 这时候,可以使用@Validated
进行分组验证。
----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------
标签:return,spring,boot,校验,value,public,Valid,private,class From: https://www.cnblogs.com/hanease/p/16951568.html