SpringBoot使用Validation校驗參數(shù)的詳細過程
JSR(Java Specification Requests)是Java界的重要標準;JSR又細分很多標準,其中JSR303就代表Bean Validation。更多細節(jié)可參考:https://jcp.org/en/jsr/detail?id=303。
準備工作
引入相關(guān)依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>注:本人測試時,還引入了lombok、SpringBoot的web、test等基礎(chǔ)依賴,這里就不一一給出了。
約束性注解(簡單)說明
注解 | 功能 |
@AssertFalse | 可以為null,如果不為null的話必須為false |
@AssertTrue | 可以為null,如果不為null的話必須為true |
@DecimalMax | 設(shè)置不能超過最大值 |
@DecimalMin | 設(shè)置不能超過最小值 |
@Digits | 設(shè)置必須是數(shù)字且數(shù)字整數(shù)的位數(shù)和小數(shù)的位數(shù)必須在指定范圍內(nèi) |
@Future | 日期必須在當前日期的未來 |
@Past | 日期必須在當前日期的過去 |
@Max | 最大不得超過此最大值 |
@Min | 最大不得小于此最小值 |
@NotNull | 不能為null,可以是空 |
@Null | 必須為null |
@Pattern | 必須滿足指定的正則表達式 |
@Size | 集合、數(shù)組、map等的size()值必須在指定范圍內(nèi) |
必須是email格式 | |
@Length | 長度必須在指定范圍內(nèi) |
@NotBlank | 字符串不能為null,字符串trim()后也不能等于“” |
@NotEmpty | 不能為null,集合、數(shù)組、map等size()不能為0;字符串trim()后可以等于“” |
@Range | 值必須在指定范圍內(nèi) |
@URL | 必須是一個URL |
注:此表格只是簡單的對注解功能的說明,并沒有對每一個注解的屬性進行說明;可詳見源碼。
下面簡單測試一下上述各注解:
測試所用模型為:
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;
}
}測試方法為:
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屬性 -> 數(shù)字的值超出了允許范圍(只允許在5位整數(shù)和3位小數(shù)范圍內(nèi))
*/
@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屬性 -> 個數(shù)必須在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());
}
}
}@Validated的使用時機
@Validated的使用位置較多(可詳見源碼),但其主流的使用位置卻是以下兩種:
1、在Controller層中,放在模型參數(shù)對象前。
當Controller層中參數(shù)是一個對象模型時,只有將@Validated直接放在該模型前,該模型內(nèi)部的字段才會被
校驗(如果有對該模型的字段進行約束的話)。
2、在Controller層中,放在類上。
當一些約束是直接出現(xiàn)在Controller層中的參數(shù)前時,只有將@Validated放在類上時,參數(shù)前的約束才會生效。
以下是簡單的測試代碼:
import com.aspire.model.ValidationBeanModel;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.DecimalMax;
/**
* Controller層 --- 初步簡單測試 @Validated 的使用位置
*
* 對比測試過程:
* 方案一 : 不在類上加@Validated注解,訪問這六個接口
* 方案二 : 在類上加@Validated注解,再次訪問這六個接口
*
* 對比方案一和方案二,可初步得出@Validated的使用時機:
* 1.當我們是在模型里面對模型字段添加約束注解,在Controller中使用模型接收數(shù)
* 據(jù)時,@Validated要直接放在該模型參數(shù)前才有效。 如: "/test/one"
* 2.當我們是直接在Controller層中的參數(shù)前,使用約束注解時,@Validated要直接放在類上,
* 才會有效。如: /test/six
*
*
* @author JustryDeng
* @date 2019/1/18 22:22
*/
@RestController
@Validated
public class JustryDengController {
@RequestMapping(value = "/test/one")
public String validatioOne(@Validated ValidationBeanModel.AbcDecimalMax myDecimalMax) {
System.out.println(myDecimalMax.getMyDecimalMax());
return "one pass!";
}
@RequestMapping(value = "/test/two")
@Validated
public String validatioTwo(ValidationBeanModel.AbcDecimalMax myDecimalMax) {
System.out.println(myDecimalMax.getMyDecimalMax());
return "two pass!";
}
@RequestMapping(value = "/test/three")
public String validatioThree(ValidationBeanModel.AbcDecimalMax myDecimalMax) {
System.out.println(myDecimalMax.getMyDecimalMax());
return "three pass!";
}
@RequestMapping(value = "/test/four")
public String validatioFour(@Validated @DecimalMax(value = "12.3") String myDecimalMax) {
System.out.println(myDecimalMax);
return "four pass!";
}
@RequestMapping(value = "/test/five")
@Validated
public String validatioFive(@DecimalMax(value = "12.3") String myDecimalMax) {
System.out.println(myDecimalMax);
return "five pass!";
}
@RequestMapping(value = "/test/six")
@Validated
public String validatioSix(@DecimalMax(value = "12.3") String myDecimalMax) {
System.out.println(myDecimalMax);
return "six pass!";
}
}@Validated與@Valid的簡單對比說明
@Valid注解與@Validated注解功能大部分類似;兩者的不同主要在于:@Valid屬于javax下的,而@Validated屬于spring下;@Valid支持嵌套校驗、而@Validated不支持,@Validated支持分組,而@Valid不支持。筆者這里只簡單介紹@Validated的使用時機。
自定義注解
雖然Bean Validation和Hibernate Validator已經(jīng)提供了非常豐富的校驗注解,但是在實際業(yè)務(wù)中,難免會碰到一些現(xiàn)有注解不足以校驗的情況;這時,我們可以考慮自定義Validation注解。
示例:
第一步:創(chuàng)建自定義注解
import com.aspire.constraints.impl.JustryDengValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 自定義校驗注解
* 提示:
* 1、message、contains、payload是必須要寫的
* 2、還需要什么方法可根據(jù)自己的實際業(yè)務(wù)需求,自行添加定義即可
*
* 注:當沒有指定默認值時,那么在使用此注解時,就必須輸入對應(yīng)的屬性值
*
* @author JustryDeng
* @date 2019/1/15 1:17
*/
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Documented
// 指定此注解的實現(xiàn),即:驗證器
@Constraint(validatedBy ={JustryDengValidator.class})
public @interface ConstraintsJustryDeng {
// 當驗證不通過時的提示信息
String message() default "JustryDeng : param value must contais specified value!";
// 根據(jù)實際需求定的方法
String contains() default "";
// 約束注解在驗證時所屬的組別
Class<?>[] groups() default { };
// 負載
Class<? extends Payload>[] payload() default { };
}第二步:編寫(第一步中的校驗器實現(xiàn)類)該注解
import com.aspire.constraints.anno.ConstraintsJustryDeng;
import org.hibernate.validator.internal.engine.ValidationContext;
import org.hibernate.validator.internal.engine.ValueContext;
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
/**
* ConstraintsJustryDeng注解 校驗器 實現(xiàn)
* <p>
* 注:驗證器需要實現(xiàn)ConstraintValidator<U, V>, 其中 U為對應(yīng)的注解類, V為被該注解標記的字段的類型(或其父類型)
*
* 注: 當項目啟動后,會(懶加載)創(chuàng)建ConstraintValidator實例,在創(chuàng)建實例后會初始化調(diào)
* 用{@link ConstraintValidator#initialize}方法。
* 所以, 只有在第一次請求時,會走initialize方法, 后面的請求是不會走initialize方法的。
*
* 注: (懶加載)創(chuàng)建ConstraintValidator實例時, 會走緩存; 如果緩存中有,則直接使用相
* 同的ConstraintValidator實例; 如果緩存中沒有,那么會創(chuàng)建新的ConstraintValidator實例。
* 由于緩存的key是能唯一定位的, 且 ConstraintValidator的實例屬性只有在
* {@link ConstraintValidator#initialize}方法中才會寫;在{@link ConstraintValidator#isValid}
* 方法中只是讀。
* 所以不用擔(dān)心線程安全問題。
*
* 注: 如何創(chuàng)建ConstraintValidator實例的,可詳見源碼
* @see ConstraintTree#getInitializedConstraintValidator(ValidationContext, ValueContext)
*
* @author JustryDeng
* @date 2019/1/15 1:19
*/
public class JustryDengValidator implements ConstraintValidator<ConstraintsJustryDeng, Object> {
/** 錯誤提示信息 */
private String contains;
/**
* 初始化方法, 在(懶加載)創(chuàng)建一個當前類實例后,會馬上執(zhí)行此方法
*
* 注: 此方法只會執(zhí)行一次,即:創(chuàng)建實例后馬上執(zhí)行。
*
* @param constraintAnnotation
* 注解信息模型,可以從該模型中獲取注解類中定義的一些信息,如默認值等
* @date 2019/1/19 11:27
*/
@Override
public void initialize(ConstraintsJustryDeng constraintAnnotation) {
System.out.println(constraintAnnotation.message());
this.contains = constraintAnnotation.contains();
}
/**
* 校驗方法, 每個需要校驗的請求都會走這個方法
*
* 注: 此方法可能會并發(fā)執(zhí)行,需要根據(jù)實際情況看否是需要保證線程安全。
*
* @param value
* 被校驗的對象
* @param context
* 上下文
*
* @return 校驗是否通過
*/
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) {
return false;
}
if (value instanceof String) {
String strMessage = (String) value;
return strMessage.contains(contains);
} else if (value instanceof Integer) {
return contains.contains(String.valueOf(value));
}
return false;
}
}第三步:自定義注解簡單使用測試
Controller層中是這樣的:

訪問一下這個接口(當參數(shù)值符合要求時):

訪問一下這個接口(當參數(shù)值不符合要求時):

對注解拋出的異常進行處理
說明:當注解校驗不通過時,直接將異常信息返回給前端其實并不友好,我們可以將異常包裝一下,返回給前端。
情況一:使用BindingResult類來容納異常信息,當校驗不通過時,不影響正常程
序往下走。我們只需要處理BindingResult中的異常信息即可。
處理前:
參數(shù)模型是這樣的:

Controller是這樣的:

使用postman測試,當校驗不通過時顯示如下:

處理后:
參數(shù)模型是這樣的(沒有變):

Controller是這樣的(在@Validated注解的參數(shù)后,緊接著加上BindingResult):

再次使用postman測試,當校驗不通過時顯示如下:

postman中返回了數(shù)據(jù),說明雖然參數(shù)錯誤,但是不影響程序的執(zhí)行。
程序在控制臺輸出了如下信息:

可見,后臺已經(jīng)獲悉了錯誤,至于怎么處理,這就看偉大的程序員們了。
情況二(推薦):通過SpringMVC全局異常處理器來處理異常。
描述:如果不采用BindingResult來容納異常信息時,那么異常會被向外拋出。注解校驗不通過時,可能拋出的 異常有BindException異常、ValidationException異常(或其子類異常)、 MethodArgumentNotValidException異常。
處理前:
示例一:Controller和對應(yīng)的參數(shù)模型是這樣的:


使用postman測試,當校驗不通過時顯示如下:

示例二:Controller是這樣的:

使用postman測試,當校驗不通過時顯示如下:

注:ConstraintViolationException異常是ViolationException異常的子異常。
示例三:Controller是這樣的:

注:此處的User模型與情況一里給出的模型是一樣的,這里就不再給出了。
使用postman測試,當校驗不通過時顯示如下:

進行處理:加入AOP全局異常處理器:
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import java.util.HashMap;
import java.util.Map;
/**
* SpringMVC統(tǒng)一異常處理
*
* @author JustryDeng
* @date 2019/10/12 16:28
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理Validated校驗異常
* <p>
* 注: 常見的ConstraintViolationException異常, 也屬于ValidationException異常
*
* @param e
* 捕獲到的異常
* @return 返回給前端的data
*/
@ResponseStatus(code = HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public Map<String, Object> handleParameterVerificationException(Exception e) {
log.error(" handleParameterVerificationException has been invoked", e);
Map<String, Object> resultMap = new HashMap<>(4);
resultMap.put("code", "100001");
String msg = null;
if (e instanceof MethodArgumentNotValidException) {
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
// getFieldError獲取的是第一個不合法的參數(shù)(P.S.如果有多個參數(shù)不合法的話)
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null) {
msg = fieldError.getDefaultMessage();
}
} else if (e instanceof BindException) {
// getFieldError獲取的是第一個不合法的參數(shù)(P.S.如果有多個參數(shù)不合法的話)
FieldError fieldError = ((BindException) e).getFieldError();
if (fieldError != null) {
msg = fieldError.getDefaultMessage();
}
} else if (e instanceof ConstraintViolationException) {
/*
* ConstraintViolationException的e.getMessage()形如
* {方法名}.{參數(shù)名}: {message}
* 這里只需要取后面的message即可
*/
msg = e.getMessage();
if (msg != null) {
int lastIndex = msg.lastIndexOf(':');
if (lastIndex >= 0) {
msg = msg.substring(lastIndex + 1).trim();
}
}
/// ValidationException 的其它子類異常
} else {
msg = "處理參數(shù)時異常";
}
resultMap.put("msg", msg);
return resultMap;
}
}處理后,使用postman再次進行上述兩個請求:



可見,異常處理成功!
【補充】多級嵌套模型的校驗
假設(shè):
有api:

有Company和Employee類:


那么,當Department里面既有普通字段,又有類字段時:

解決方式是,給復(fù)雜對象字段加上@Valid注解:

【補充】groups分組校驗
在很多時候,同一個模型可能會在多處被用到,但每處的校驗場景又不一定相同(如:新增用戶接口、修改用戶接口,參數(shù)都是User模型,在新增時User中name字段不能為空,userNo字段可以為空;在修改時User中name字段可以為空,userNo字段不能為空)。我們可以用groups來實現(xiàn):同一個模型在不同場景下,(動態(tài)區(qū)分)校驗?zāi)P椭械牟煌侄巍?/p>
提示:實現(xiàn)groups功能的主要邏輯源碼,可詳見org.hibernate.validator.internal.engine.ValidatorImpl#validate。
使用方式(示例說明)
準備工作:自定義兩個分組。
提示:繼承Default并不是必須的。只是說,如果繼承了Default,那么@Validated(value = Create.class)的校驗范疇就
為【Create】和【Default】;
如果沒繼承Default,那么@Validated(value = Create.class)的校驗范疇只
為【Create】,而@Validated(value = {Create.class, Default.class})的校驗范疇才為【Create】和【Default】。
追注:原因可見下面的第二步中的說明。


第一步:(在給模型里面的參數(shù)定義校驗規(guī)則時,)給校驗分配所屬分組。

注:Default組和無參構(gòu)造機制類似,當沒有指定分組時,會默認當前校驗屬于Default組,但是一旦主動給當前校驗指定
了分組(如上圖中的name字段,主動指定了屬于Create組),那么就不會再額外指定屬于Default組了。
追注:當然,也可以畫蛇添足的主動指定所屬分組為Default。
第二步:啟動校驗時,指定校驗(組別)范疇。

為更直觀的理解,再給出一張圖:

注:還有一個用于聯(lián)合校驗(如:字段A的校驗,依賴于字段B的校驗)等場景的注解javax.validation.GroupSequence 有時也非常實用,感興趣的可自行了解。
到此這篇關(guān)于SpringBoot使用Validation校驗參數(shù)的文章就介紹到這了,更多相關(guān)SpringBoot使用Validation校驗參數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot使用validation-api實現(xiàn)參數(shù)校驗的示例
- SpringBoot集成validation校驗參數(shù)遇到的坑
- SpringBoot集成Validation參數(shù)校驗
- SpringBoot使用validation做參數(shù)校驗說明
- SpringBoot?中使用?Validation?校驗參數(shù)的方法詳解
- SpringBoot利用validation實現(xiàn)優(yōu)雅的校驗參數(shù)
- SpringBoot?Validation提示信息國際化配置方式
- SpringBoot使用Validation進行參數(shù)校驗的示例詳解
- springboot中使用Hibernate-Validation校驗參數(shù)詳解
- SpringBoot Validation入?yún)⑿r瀲H化的項目實踐
相關(guān)文章
springmvc實現(xiàn)json交互-requestBody和responseBody
本文主要介紹了springmvc實現(xiàn)json交互-requestBody和responseBody的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-03-03
Java 刪除與重置 Excel 文件密碼的完整示例(文件級與工作表級示例)
本文詳細介紹了如何使用Java實現(xiàn)Excel文件密碼的刪除與重置,涵蓋了文件和工作表級別的保護機制,通過使用Spire.XLSforJava組件,可以高效地完成這些操作,確保數(shù)據(jù)的安全性和系統(tǒng)的穩(wěn)定性,感興趣的朋友跟隨小編一起看看吧2026-02-02
SpringBoot整合Mybatis與MybatisPlus方法詳細講解
這篇文章主要介紹了SpringBoot整合Mybatis與MybatisPlus方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01
Java代碼統(tǒng)計網(wǎng)站中不同省份用戶的訪問數(shù)
這篇文章主要介紹了Java代碼統(tǒng)計網(wǎng)站中不同省份用戶的訪問數(shù) 的相關(guān)資料,非常具有參考借鑒價值,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
一文詳解SpringBoot如何使用pageHelper做分頁處理
分頁是常見大型項目都需要的一個功能,PageHelper是一個非常流行的MyBatis分頁插件,下面就跟隨小編一起來了解下SpringBoot是如何使用pageHelper做分頁處理的吧2025-03-03

