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

訪問(wèn)一下這個(gè)接口(當(dāng)參數(shù)值符合要求時(shí)):

訪問(wèn)一下這個(gè)接口(當(dāng)參數(shù)值不符合要求時(shí)):

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

Controller是這樣的:

使用postman測(cè)試,當(dāng)校驗(yàn)不通過(guò)時(shí)顯示如下:

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

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

再次使用postman測(cè)試,當(dāng)校驗(yàn)不通過(guò)時(shí)顯示如下:

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

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


使用postman測(cè)試,當(dāng)校驗(yàn)不通過(guò)時(shí)顯示如下:

示例二:Controller是這樣的:

使用postman測(cè)試,當(dāng)校驗(yàn)不通過(guò)時(shí)顯示如下:

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

注:此處的User模型與情況一里給出的模型是一樣的,這里就不再給出了。
使用postman測(cè)試,當(dāng)校驗(yàn)不通過(guò)時(shí)顯示如下:

進(jìn)行處理:加入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校驗(yàn)異常
* <p>
* 注: 常見(jiàn)的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獲取的是第一個(gè)不合法的參數(shù)(P.S.如果有多個(gè)參數(shù)不合法的話)
FieldError fieldError = bindingResult.getFieldError();
if (fieldError != null) {
msg = fieldError.getDefaultMessage();
}
} else if (e instanceof BindException) {
// getFieldError獲取的是第一個(gè)不合法的參數(shù)(P.S.如果有多個(gè)參數(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 的其它子類(lèi)異常
} else {
msg = "處理參數(shù)時(shí)異常";
}
resultMap.put("msg", msg);
return resultMap;
}
}處理后,使用postman再次進(jìn)行上述兩個(gè)請(qǐng)求:



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

有Company和Employee類(lèi):


那么,當(dāng)Department里面既有普通字段,又有類(lèi)字段時(shí):

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

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


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

注:Default組和無(wú)參構(gòu)造機(jī)制類(lèi)似,當(dāng)沒(méi)有指定分組時(shí),會(huì)默認(rèn)當(dāng)前校驗(yàn)屬于Default組,但是一旦主動(dòng)給當(dāng)前校驗(yàn)指定
了分組(如上圖中的name字段,主動(dòng)指定了屬于Create組),那么就不會(huì)再額外指定屬于Default組了。
追注:當(dāng)然,也可以畫(huà)蛇添足的主動(dòng)指定所屬分組為Default。
第二步:啟動(dòng)校驗(yàn)時(shí),指定校驗(yàn)(組別)范疇。

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

注:還有一個(gè)用于聯(lián)合校驗(yàn)(如:字段A的校驗(yàn),依賴(lài)于字段B的校驗(yàn))等場(chǎng)景的注解javax.validation.GroupSequence 有時(shí)也非常實(shí)用,感興趣的可自行了解。
到此這篇關(guān)于SpringBoot使用Validation校驗(yàn)參數(shù)的文章就介紹到這了,更多相關(guān)SpringBoot使用Validation校驗(yàn)參數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot使用validation-api實(shí)現(xiàn)參數(shù)校驗(yàn)的示例
- SpringBoot集成validation校驗(yàn)參數(shù)遇到的坑
- SpringBoot集成Validation參數(shù)校驗(yàn)
- SpringBoot使用validation做參數(shù)校驗(yàn)說(shuō)明
- SpringBoot?中使用?Validation?校驗(yàn)參數(shù)的方法詳解
- SpringBoot利用validation實(shí)現(xiàn)優(yōu)雅的校驗(yàn)參數(shù)
- SpringBoot?Validation提示信息國(guó)際化配置方式
- SpringBoot使用Validation進(jìn)行參數(shù)校驗(yàn)的示例詳解
- springboot中使用Hibernate-Validation校驗(yàn)參數(shù)詳解
- SpringBoot Validation入?yún)⑿r?yàn)國(guó)際化的項(xiàng)目實(shí)踐
相關(guān)文章
idea中Stash與Unstash的使用及說(shuō)明
這篇文章主要介紹了idea中Stash與Unstash的使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02
springmvc實(shí)現(xiàn)json交互-requestBody和responseBody
本文主要介紹了springmvc實(shí)現(xiàn)json交互-requestBody和responseBody的相關(guān)知識(shí)。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-03-03
Java 刪除與重置 Excel 文件密碼的完整示例(文件級(jí)與工作表級(jí)示例)
本文詳細(xì)介紹了如何使用Java實(shí)現(xiàn)Excel文件密碼的刪除與重置,涵蓋了文件和工作表級(jí)別的保護(hù)機(jī)制,通過(guò)使用Spire.XLSforJava組件,可以高效地完成這些操作,確保數(shù)據(jù)的安全性和系統(tǒng)的穩(wěn)定性,感興趣的朋友跟隨小編一起看看吧2026-02-02
SpringBoot整合Mybatis與MybatisPlus方法詳細(xì)講解
這篇文章主要介紹了SpringBoot整合Mybatis與MybatisPlus方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01
SpringBoot有外部依賴(lài)如何打運(yùn)行Jar包的問(wèn)題
文章描述了如何將外部依賴(lài)導(dǎo)入本地Maven倉(cāng)庫(kù),并在pom文件中進(jìn)行配置,以及使用maven-assembly-plugin來(lái)打包Jar文件的方法,強(qiáng)調(diào)了在執(zhí)行命令時(shí)需要注意拼寫(xiě)錯(cuò)誤,并保持pom文件中的配置與mvn?install命令一致,最后提到成功打包Jar文件,并確認(rèn)Jar可以正常運(yùn)行2025-10-10
Java代碼統(tǒng)計(jì)網(wǎng)站中不同省份用戶的訪問(wèn)數(shù)
這篇文章主要介紹了Java代碼統(tǒng)計(jì)網(wǎng)站中不同省份用戶的訪問(wèn)數(shù) 的相關(guān)資料,非常具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-05-05
SpringBoot使用注解進(jìn)行分頁(yè)的實(shí)現(xiàn)示例
分頁(yè)使用可以說(shuō)非常普遍了,有時(shí)候會(huì)需要非常靈活的方式去開(kāi)啟或關(guān)閉分頁(yè),嘗試使用一下注解的方式來(lái)進(jìn)行分頁(yè),本文主要介紹了SpringBoot使用注解進(jìn)行分頁(yè)的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
一文詳解SpringBoot如何使用pageHelper做分頁(yè)處理
分頁(yè)是常見(jiàn)大型項(xiàng)目都需要的一個(gè)功能,PageHelper是一個(gè)非常流行的MyBatis分頁(yè)插件,下面就跟隨小編一起來(lái)了解下SpringBoot是如何使用pageHelper做分頁(yè)處理的吧2025-03-03

