SpringBoot使用Validation校驗參數(shù)的詳細過程
JSR(Java Specification Requests)是Java界的重要標(biāo)準;JSR又細分很多標(biāo)準,其中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 | 日期必須在當(dāng)前日期的未來 |
@Past | 日期必須在當(dāng)前日期的過去 |
@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ù)對象前。
當(dāng)Controller層中參數(shù)是一個對象模型時,只有將@Validated直接放在該模型前,該模型內(nèi)部的字段才會被
校驗(如果有對該模型的字段進行約束的話)。
2、在Controller層中,放在類上。
當(dāng)一些約束是直接出現(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.當(dāng)我們是在模型里面對模型字段添加約束注解,在Controller中使用模型接收數(shù) * 據(jù)時,@Validated要直接放在該模型參數(shù)前才有效。 如: "/test/one" * 2.當(dāng)我們是直接在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ù)需求,自行添加定義即可 * * 注:當(dāng)沒有指定默認值時,那么在使用此注解時,就必須輸入對應(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 { // 當(dāng)驗證不通過時的提示信息 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為被該注解標(biāo)記的字段的類型(或其父類型) * * 注: 當(dāng)項目啟動后,會(懶加載)創(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)建一個當(dā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層中是這樣的:
訪問一下這個接口(當(dāng)參數(shù)值符合要求時):
訪問一下這個接口(當(dāng)參數(shù)值不符合要求時):
對注解拋出的異常進行處理
說明:當(dāng)注解校驗不通過時,直接將異常信息返回給前端其實并不友好,我們可以將異常包裝一下,返回給前端。
情況一:使用BindingResult類來容納異常信息,當(dāng)校驗不通過時,不影響正常程
序往下走。我們只需要處理BindingResult中的異常信息即可。
處理前:
參數(shù)模型是這樣的:
Controller是這樣的:
使用postman測試,當(dāng)校驗不通過時顯示如下:
處理后:
參數(shù)模型是這樣的(沒有變):
Controller是這樣的(在@Validated注解的參數(shù)后,緊接著加上BindingResult):
再次使用postman測試,當(dāng)校驗不通過時顯示如下:
postman中返回了數(shù)據(jù),說明雖然參數(shù)錯誤,但是不影響程序的執(zhí)行。
程序在控制臺輸出了如下信息:
可見,后臺已經(jīng)獲悉了錯誤,至于怎么處理,這就看偉大的程序員們了。
情況二(推薦):通過SpringMVC全局異常處理器來處理異常。
描述:如果不采用BindingResult來容納異常信息時,那么異常會被向外拋出。注解校驗不通過時,可能拋出的 異常有BindException異常、ValidationException異常(或其子類異常)、 MethodArgumentNotValidException異常。
處理前:
示例一:Controller和對應(yīng)的參數(shù)模型是這樣的:
使用postman測試,當(dāng)校驗不通過時顯示如下:
示例二:Controller是這樣的:
使用postman測試,當(dāng)校驗不通過時顯示如下:
注:ConstraintViolationException異常是ViolationException異常的子異常。
示例三:Controller是這樣的:
注:此處的User模型與情況一里給出的模型是一樣的,這里就不再給出了。
使用postman測試,當(dāng)校驗不通過時顯示如下:
進行處理:加入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類:
那么,當(dāng)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)造機制類似,當(dāng)沒有指定分組時,會默認當(dāng)前校驗屬于Default組,但是一旦主動給當(dāng)前校驗指定
了分組(如上圖中的name字段,主動指定了屬于Create組),那么就不會再額外指定屬于Default組了。
追注:當(dāng)然,也可以畫蛇添足的主動指定所屬分組為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)文章
Spring Data JPA例子代碼[基于Spring Boot、Mysql]
這篇文章主要介紹了Spring Data JPA例子代碼[基于Spring Boot、Mysql],小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04SpringBoot整合MongoDB實現(xiàn)文件上傳下載刪除
這篇文章主要介紹了SpringBoot整合MongoDB實現(xiàn)文件上傳下載刪除的方法,幫助大家更好的理解和學(xué)習(xí)使用SpringBoot框架,感興趣的朋友可以了解下2021-05-05java編程創(chuàng)建型設(shè)計模式單例模式的七種示例
這篇文章主要為大家介紹了java編程中創(chuàng)建型設(shè)計模式之單例模式的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02SpringBoot異步使用@Async的原理以及線程池配置詳解
在項目中當(dāng)訪問其他人的接口較慢時,不想程序一直卡在耗時任務(wù)上,想程序能夠并行執(zhí)行,我們可以使用多線程來并行的處理任務(wù),也可以使用spring提供的異步處理方式@Async,這篇文章主要給大家介紹了關(guān)于SpringBoot異步使用@Async的原理以及線程池配置的相關(guān)資料2021-09-09