spring validation校验参数
一、前言
数据的校验是交互式网站一个不可或缺的功能,前端的js校验可以涵盖大部分的校验职责,如用户名唯一性,生日格式,邮箱格式校验等等常用的校验。但是为了避免用户绕过浏览器,使用http工具直接向后端请求一些违法数据,服务端的数据校验也是必要的,可以防止脏数据落到数据库中,如果数据库中出现一个非法的邮箱格式,也会让运维人员头疼不已。可以使用本文将要介绍的validation来对数据进行校验。
二、常用校验
1、JSR303/JSR-349
JSR303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349是其的升级版本,添加了一些新特性。
@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) //被注释的元素必须符合指定的正则表达式
2、hibernate validation
hibernate validation是对这个规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等
@Email //被注释的元素必须是电子邮箱地址 @Length(min=, max=) //被注释的字符串的大小必须在指定的范围内 @NotEmpty //被注释的字符串的必须非空 @Range(min=, max=) //被注释的元素必须在合适的范围内 @NotBlank //字符串不能为null,字符串trin()后也不能等于“” @URL(protocol=,host=, port=, regexp=, flags=) //被注释的字符串必须是一个有效的url
3、spring validation
spring validation对hibernate validation进行了二次封装,在springmvc模块中添加了自动校验,并将校验信息封装进了特定的类中。
package com.example.validation.domain; import javax.validation.constraints.Pattern; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import org.hibernate.validator.constraints.Range; public class User { @NotBlank(message = "用户名称不能为空。") private String name; @Range(max = 150, min = 1, message = "年龄范围应该在1-150内。") private Integer age; @NotEmpty(message = "密码不能为空") @Length(min = 6, max = 8, message = "密码长度为6-8位。") @Pattern(regexp = "[a-zA-Z]*", message = "密码不合法") private String password;
三、测试
1、准备工作
引入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> /dependency>
还引入了lombok、SpringBoot的web、test等基础依赖,这里就不一 一给出了。
2、测试所用模型为:
import lombok.Getter; import lombok.Setter; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.URL; import javax.validation.constraints.*; import java.util.Date; import java.util.List; import java.util.Map; /** * Validation注解 * * @author JustryDeng * @date 2019/1/15 0:43 */ public class ValidationBeanModel { @Setter @Getter public class AbcAssertFalse { @AssertFalse private Boolean myAssertFalse; } @Setter @Getter public class AbcAssertTrue { @AssertTrue private Boolean myAssertTrue; } @Setter @Getter public class AbcDecimalMax { @DecimalMax(value = "12.3") private String myDecimalMax; } @Setter @Getter public class AbcDecimalMin { @DecimalMin(value = "10.3") private String myDecimalMin; } @Setter @Getter public class AbcDigits { @Digits(integer = 5, fraction = 3) private Integer myDigits; } @Setter @Getter public class AbcEmail { @Email private String myEmail; } @Setter @Getter public class AbcFuture { @Future private Date myFuture; } @Setter @Getter public class AbcLength { @Length(min = 5, max = 10) private String myLength; } @Setter @Getter public class AbcMax { @Max(value = 200) private Long myMax; } @Setter @Getter public class AbcMin { @Min(value = 100) private Long myMin; } @Setter @Getter public class AbcNotBlank { @NotBlank private String myStringNotBlank; @NotBlank private String myObjNotBlank; } @Setter @Getter public class AbcNotEmpty { @NotEmpty private String myStringNotEmpty; @NotEmpty private String myNullNotEmpty; @NotEmpty private Map<String, Object> myMapNotEmpty; @NotEmpty private List<Object> myListNotEmpty; @NotEmpty private Object[] myArrayNotEmpty; } @Setter @Getter public class AbcNotNull { @NotNull private String myStringNotNull; @NotNull private Object myNullNotNull; @NotNull private Map<String, Object> myMapNotNull; } @Setter @Getter public class AbcNull { @Null private String myStringNull; @Null private Map<String, Object> myMapNull; } @Setter @Getter public class AbcPast { @Past private Date myPast; } @Setter @Getter public class AbcPattern { @Pattern(regexp = "\\d+") private String myPattern; } @Setter @Getter public class AbcRange { @Range(min = 100, max = 100000000000L) private Double myRange; } @Setter @Getter public class AbcSize { @Size(min = 3, max = 5) private List<Integer> mySize; } @Setter @Getter public class AbcURL { @URL private String myURL; } }
3、测试方法
import com.aspire.model.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import java.util.*; @RunWith(SpringRunner.class) @SpringBootTest public class ValidationDemoApplicationTests { private Validator validator; @Before public void initValidator() { ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); validator = validatorFactory.getValidator(); } /** * 在myAssertTrue属性上加@AssertTrue注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcAssertTrue类的myAssertTrue属性 -> 只能为true */ @Test public void testAssertTrue() { ValidationBeanModel.AbcAssertTrue vm = new ValidationBeanModel().new AbcAssertTrue(); vm.setMyAssertTrue(false); fa(vm); } /** * 在myAssertFalse属性上加@AssertFalse注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcAssertFalse类的myAssertFalse属性 -> 只能为false */ @Test public void testAssertFalse() { ValidationBeanModel.AbcAssertFalse vm = new ValidationBeanModel().new AbcAssertFalse(); vm.setMyAssertFalse(true); fa(vm); } /** * 在myDecimalMax属性上加@DecimalMax(value = "12.3")注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcDecimalMax类的myDecimalMax属性 -> 必须小于或等于12.3 */ @Test public void testDecimalMax() { ValidationBeanModel.AbcDecimalMax vm = new ValidationBeanModel().new AbcDecimalMax(); vm.setMyDecimalMax("123"); fa(vm); } /** * 在myDecimalMin属性上加@DecimalMin(value = "10.3")注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcDecimalMin类的myDecimalMin属性 -> 必须大于或等于10.3 */ @Test public void testDecimalMin() { ValidationBeanModel.AbcDecimalMin vm = new ValidationBeanModel().new AbcDecimalMin(); vm.setMyDecimalMin("1.23"); fa(vm); } /** * 在myDigits属性上加@Digits(integer = 5, fraction = 3)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcDigits类的myDigits属性 -> 数字的值超出了允许范围(只允许在5位整数和3位小数范围内) */ @Test public void testDigits() { ValidationBeanModel.AbcDigits vm = new ValidationBeanModel().new AbcDigits(); vm.setMyDigits(1000738); fa(vm); } /** * 在myEmail属性上加@Email注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcEmail类的myEmail属性 -> 不是一个合法的电子邮件地址 */ @Test public void testEmail() { ValidationBeanModel.AbcEmail vm = new ValidationBeanModel().new AbcEmail(); vm.setMyEmail("asd@.com"); fa(vm); } /** * 在myFuture属性上加@Future注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcFuture类的myFuture属性 -> 需要是一个将来的时间 */ @Test public void testFuture() { ValidationBeanModel.AbcFuture vm = new ValidationBeanModel().new AbcFuture(); vm.setMyFuture(new Date(10000L)); fa(vm); } /** * 在myLength属性上加@Length(min = 5, max = 10)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcLength类的myLength属性 -> 长度需要在5和10之间 */ @Test public void testLength() { ValidationBeanModel.AbcLength vm = new ValidationBeanModel().new AbcLength(); vm.setMyLength("abcd"); fa(vm); } /** * 在myMax属性上加@Max(value = 200)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcMax类的myMax属性 -> 最大不能超过200 */ @Test public void testMax() { ValidationBeanModel.AbcMax vm = new ValidationBeanModel().new AbcMax(); vm.setMyMax(201L); fa(vm); } /** * 在myMin属性上加@Min(value = 200)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcMin类的myMin属性 -> 最小不能小于100 */ @Test public void testMin() { ValidationBeanModel.AbcMin vm = new ValidationBeanModel().new AbcMin(); vm.setMyMin(99L); fa(vm); } /** * 在myStringNotBlank属性上加@NotBlank注解 * 在myObjNotBlank属性上加@NotBlank注解 * * 注:如果属性值为null 或者 .trim()后等于"",那么会提示 不能为空 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotBlank类的myObjNotBlank属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotBlank类的myStringNotBlank属性 -> 不能为空 */ @Test public void testNotBlank() { ValidationBeanModel.AbcNotBlank vm = new ValidationBeanModel().new AbcNotBlank(); vm.setMyObjNotBlank(null); vm.setMyStringNotBlank(" "); fa(vm); } /** * 在myStringNotEmpty属性上加@NotEmpty注解 * 在myNullNotEmpty属性上加@NotEmpty注解 * 在myMapNotEmpty属性上加@NotEmpty注解 * 在myListNotEmpty属性上加@NotEmpty注解 * 在myArrayNotEmpty属性上加@NotEmpty注解 * * 注:String可以是.trim()后等于""的字符串,但是不能为null * 注:MAP、Collection、Array既不能是空,也不能是null * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myNullNotEmpty属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myListNotEmpty属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myArrayNotEmpty属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myMapNotEmpty属性 -> 不能为空 */ @Test public void testNotEmpty() { ValidationBeanModel.AbcNotEmpty vm = new ValidationBeanModel().new AbcNotEmpty(); vm.setMyStringNotEmpty(" "); vm.setMyNullNotEmpty(null); vm.setMyMapNotEmpty(new HashMap<>(0)); vm.setMyListNotEmpty(new ArrayList<>(0)); vm.setMyArrayNotEmpty(new String[]{}); fa(vm); } /** * 在myStringNotNull属性上加@NotNull注解 * 在myNullNotNull属性上加@NotNull注解 * 在myMapNotNull属性上加@NotNull注解 * * 注:属性值可以是空的, 但是就是不能为null * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotNull类的myNullNotNull属性 -> 不能为null */ @Test public void testNotNull() { ValidationBeanModel.AbcNotNull vm = new ValidationBeanModel().new AbcNotNull(); vm.setMyStringNotNull(" "); vm.setMyNullNotNull(null); vm.setMyMapNotNull(new HashMap<>(0)); fa(vm); } /** * 在myStringNull属性上加@Null注解 * 在myMapNotNull属性上加@Null注解 * * 注:属性值必须是null, 是空都不行 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNull类的myMapNull属性 -> 必须为null * com.aspire.model.ValidationBeanModel$AbcNull类的myStringNull属性 -> 必须为null */ @Test public void testNull() { ValidationBeanModel.AbcNull vm = new ValidationBeanModel().new AbcNull(); vm.setMyStringNull(" "); vm.setMyMapNull(new HashMap<>(0)); fa(vm); } /** * 在myPast属性上加@Past注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcPast类的myPast属性 -> 需要是一个过去的时间 */ @Test public void testPast() { ValidationBeanModel.AbcPast vm = new ValidationBeanModel().new AbcPast(); vm.setMyPast(new Date(20000000000000000L)); fa(vm); } /** * 在myPattern属性上加@Pattern(regexp = "\\d+")注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcPattern类的myPattern属性 -> 需要匹配正则表达式"\d" */ @Test public void testPattern() { ValidationBeanModel.AbcPattern vm = new ValidationBeanModel().new AbcPattern(); vm.setMyPattern("ABC"); fa(vm); } /** * 在myRange属性上加@Range(min = 100, max = 100000000000L)注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcRange类的myRange属性 -> 需要在100和100000000000之间 */ @Test public void testRange() { ValidationBeanModel.AbcRange vm = new ValidationBeanModel().new AbcRange(); vm.setMyRange(32222222222222222222222222222222.323); fa(vm); } /** * 在mySize属性上加@Size(min = 3, max = 5)注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcSize类的mySize属性 -> 个数必须在3和5之间 */ @Test public void testSize() { ValidationBeanModel.AbcSize vm = new ValidationBeanModel().new AbcSize(); List<Integer> list = new ArrayList<>(4); list.add(0); list.add(1); vm.setMySize(list); fa(vm); } /** * 在myURL属性上加@URL注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcURL类的myURL属性 -> 需要是一个合法的URL */ @Test public void testURL() { ValidationBeanModel.AbcURL vm = new ValidationBeanModel().new AbcURL(); vm.setMyURL("www.baidu.xxx"); fa(vm); } private <T> void fa(T obj) { Set<ConstraintViolation<T>> cvSet = validator.validate(obj); for (ConstraintViolation<T> cv : cvSet) { System.err.println(cv.getRootBean().getClass().getName() + "类的" + cv.getPropertyPath() + "属性 -> " + cv.getMessage()); } } }
4、@Validated的使用时机
@Validated的使用位置较多(可详见源码),但其主流的使用位置是以下两种:
- 在Controller层中,放在模型参数对象前。
当Controller层中参数是一个对象模型时,只有将@Validated直接放在该模型前,该模型内部的字段才会被
校验(如果有对该模型的字段进行约束的话)。
@RestController public class JustryDengController { @RequestMapping(value = "/test/one") public String validatioOne(@Validated @RequestBody User user) { System.out.println(myDecimalMax.getMyDecimalMax()); return "one pass!"; } }
- 在Controller层中,放在类上。
当一些约束是直接出现在Controller层中的参数前时,只有将@Validated放在类上时,参数前的约束才会生效。
@RestController @Validated public class Controller{ @RequestMapping(value = "/test/two") public String validatioTwo(@NotBlank String name) { return "two pass!"; } }
四、spring boot的数据自动校验功能
1、引入依赖
spring-web模块使用了hibernate-validation,并且databind模块也提供了相应的数据绑定功能。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
我们只需要引入spring-boot-starter-web依赖即可,如果查看其子依赖,可以发现如下的依赖:
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
2、创建需要被校验的实体类
public class Person { @NotEmpty(message = "name不能为空") private String name; @Range(min = 0, max = 100, message = "age不能大于100小于0") private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }
3、在Controller中校验数据
springmvc为我们提供了自动封装表单参数的功能,一个添加了参数校验的典型controller如下所示。
@RequestMapping("/test") public String valid(@Validated Person person, BindingResult bindingResult) { if (bindingResult.hasErrors()) { for (FieldError fieldError : bindingResult.getFieldErrors()) { System.out.println(fieldError); } return "fail"; } return "success"; }
值得注意的地方:
- 参数Persion前需要加上@Validated注解,表明需要spring对其进行校验,而校验的信息会存放到其后的BindingResult中。注意,必须相邻,如果有多个参数需要校验,形式可以如下。valid(@Validated Person person, BindingResult fooBindingResult ,@Validated Bar bar, BindingResult barBindingResult);即一个校验类对应一个校验结果。
- 校验结果会被自动填充,在controller中可以根据业务逻辑来决定具体的操作,如跳转到错误页面。 一个最基本的校验就完成了.
启动容器测试结果如下:
Field error in object 'person' on field 'age': rejected value [105]; codes [Range.person.age,Range.age,Range.int,Range]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.age,age]; arguments []; default message [age],100,0]; default message [age不能大于100小于0]
4、统一异常处理
前面那种方式处理校验错误,略显复杂,而且一般网站都会对请求错误做统一的404页面封装,如果数据校验不通过,则Spring boot会抛出BindException异常,我们可以捕获这个异常并使用Result封装返回结果。通过@RestControllerAdvice定义异常捕获类。
Controller类:
@RequestMapping(value = "valid", method = RequestMethod.GET) public String valid(@Validated Person person) { System.out.println(person); return "success"; }
统一异常处理类:
@RestControllerAdvice public class BindExceptionHanlder { @ExceptionHandler(BindException.class) public String handleBindException(HttpServletRequest request, BindException exception) { List<FieldError> allErrors = exception.getFieldErrors(); StringBuilder sb = new StringBuilder(); for (FieldError errorMessage : allErrors) { sb.append(errorMessage.getField()).append(": ").append(errorMessage.getDefaultMessage()).append(", "); } System.out.println(sb.toString()); return sb.toString(); } //或者换种写法 @ExceptionHandler(value = {Exception.class}) public Map<String, Object> globalExceptionHandleMethod(Exception ex) { Map<String, Object> resultMap = new HashMap<>(4); if (ex instanceof ConstraintViolationException) { ConstraintViolationException cvExceptionex = (ConstraintViolationException) ex; resultMap.put("msg", "@Validated约束在类上,直接校验接口的参数时异常 -> " + cvExceptionex.getMessage()); resultMap.put("code", "1"); } else if (ex instanceof MethodArgumentNotValidException) { MethodArgumentNotValidException manvExceptionex = (MethodArgumentNotValidException) ex; resultMap.put("msg", "@Validated约束在参数模型前,校验该模型的字段时发生异常 -> " + manvExceptionex.getMessage()); resultMap.put("code", "2"); } else { resultMap.put("msg", "系统异常"); resultMap.put("code", "3"); } return resultMap; } /** *全局捕捉参数校验异常【validation校验】 */ @ExceptionHandler(MethodArgumentNotValidException.class) public ResultBean handleBindException(HttpServletRequest request, MethodArgumentNotValidException exception) { String defaultMessage = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage(); return ResultBean.error(defaultMessage); } }
5、自定义校验注解
@NameValidation
@Documented @Constraint(validatedBy = NameValidationValidator.class) //指定此注解的实现,即:验证器 @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RUNTIME) public @interface NameValidation { String message() default "不是合法的名字"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; @Target({PARAMETER, ANNOTATION_TYPE}) @Retention(RUNTIME) @Documented @interface List { NameValidation[] value(); } }
校验类NameValidationValidator
public class NameValidationValidator implements ConstraintValidator<NameValidation, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if ("steven".equalsIgnoreCase(value)) { return true; } String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); System.out.println("default message :" + defaultConstraintMessageTemplate); //禁用默认提示信息 //context.disableDefaultConstraintViolation(); //设置提示语 //context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); return false; } }
在Person类增加新注解
@NotEmpty(message = "name不能为空") @NameValidation private String name;
测试
6、分组校验
有些时候一个对象会在多个场景使用,不同场景对该对象中的参数校验需求不同,即有些场景不对参数进行校验。
比如注册时,我们要填写出生日期参数,但是登录时并不需要该参数。
这里可以用到校验分组groups
public class User implements Serializable { // 添加2个空接口,用例标记参数校验规则 /** * 注册校验规则 */ public interface UserRegisterValidView { } /** * 登录校验规则 */ public interface UserLoginValidView { } private static final long serialVersionUID = 1L; @NotBlank(message = "用户名不能为空") private String userName; @NotBlank(message = "密码不能为空") private String password; // 若填写了groups,则该参数验证只在对应验证规则下启用 @Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求") private Date birthday; @DecimalMin(value = "0.1", message = "金额最低为0.1") @NotNull(message = "金额不能为空") private BigDecimal balance; @AssertTrue(groups = { UserRegisterValidView.class, UserLoginValidView.class }, message = "标记必须为true") private boolean flag; @Min(value = 18, message = "年龄不能小于18") private Integer age; }
例如出生日期参数,我们只在注册场景校验,我们在其他场景(包含default)一律不进行验证
// 若填写了groups,则该参数验证只在对应验证规则下启用 @Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求") private Date birthday;
此时的Controller改成
@RequestMapping(value = "/register", method = RequestMethod.POST) @ResponseBody//表明对User对象的校验,启用UserRegisterValidView规则 public CommonResponse register(@Validated(value = { UserRegisterValidView.class }) @RequestBody User user) { CommonResponse response = new CommonResponse(); return response; }
1. 定义校验分组 //分组一 public interface ValidationGroup1 { //接口中不需要任何定义 //用户名不能为空 密码长度在6-12之间 } //分组二 public interface ValidationGroup2 { //接口中不需要任何定义 //邮件格式不正确 } 2. 在校验规则中添加分组 //分组一: 用户名不能为空 @NotEmpty(message="{user.username}",groups={ValidationGroup1.class}) public String username; //分组一:密码长度必须在6-12之间 @Length(min=6,max=12,message="{user.password}",groups={ValidationGroup1.class}) public String password; //分组二:必须符合正则表达式规则 @Email(regexp="^[_a-z0-9]+@([_a-z0-9]+\\.)+[a-z0-9]{2,3}$",message="{user.email}",groups={ValidationGroup2.class}) private String email; 3.在conroller中指定使用的分组校验 //仅使用分组一进行校验 public String insertUser(Model model,@Validated(value={ValidationGroup1.class}) User user,BindingResult bindingResult,Integer uid){} //仅使用分组二进行校验 public String insertUser(Model model,@Validated(value={ValidationGroup2.class}) User user,BindingResult bindingResult,Integer uid){} //使用分组一、分组二进行校验 public String insertUser(Model model,@Validated(value={ValidationGroup1.class,ValidationGroup2.class}) User user,BindingResult bindingResult,Integer uid){}
参数验证 @Validated 和 @Valid 的区别
Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303 规范,是标准 JSR-303 的一个变种),javax提供了@Valid(标准JSR-303规范),配合 BindingResult 可以直接提供参数验证结果。其中对于字段的特定验证注解比如 @NotNull 等网上到处都有,这里不详述
在检验 Controller 的入参是否符合规范时,使用 @Validated 或者 @Valid 在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:
1. 分组
@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@Valid:作为标准JSR-303规范,还没有吸收分组的功能。
2. 注解地方
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。
3. 嵌套验证
在比较两者嵌套验证时,先说明下什么叫做嵌套验证。比如我们现在有个实体叫做Item:
public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @NotNull(message = "props不能为空") @Size(min = 1, message = "至少要有一个属性") private List<Prop> props; }
Item带有很多属性,属性里面有属性id,属性值id,属性名和属性值,如下所示:
public class Prop { @NotNull(message = "pid不能为空") @Min(value = 1, message = "pid必须为正整数") private Long pid; @NotNull(message = "vid不能为空") @Min(value = 1, message = "vid必须为正整数") private Long vid; @NotBlank(message = "pidName不能为空") private String pidName; @NotBlank(message = "vidName不能为空") private String vidName; }
属性这个实体也有自己的验证机制,比如属性和属性值id不能为空,属性名和属性值不能为空等。
现在我们有个 ItemController 接受一个Item的入参,想要对Item进行验证,如下所示:
@RestController public class ItemController { @RequestMapping("/item/add") public void addItem(@Validated Item item, BindingResult bindingResult) { doSomething(); } }
在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。
为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。
我们修改Item类如下所示:
public class Item { @NotNull(message = "id不能为空") @Min(value = 1, message = "id必须为正整数") private Long id; @Valid // 嵌套验证必须用@Valid @NotNull(message = "props不能为空") @Size(min = 1, message = "props至少要有一个自定义属性") private List<Prop> props; }
然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。
总结一下 @Validated 和 @Valid 在嵌套验证功能上的区别:
@Validated: 用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid: 用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
参考文章:
https://blog.csdn.net/steven2xupt/article/details/87452664