一、前言
數據的校驗是交互式網站一個不可或缺的功能,前端的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
