Spring?Boot之Validation自定義實現(xiàn)方式的總結(jié)
Validation自定義實現(xiàn)方式
Spring Boot Validation定制
雖然在Spring Boot中已經(jīng)提供了非常多的預(yù)置注解,用以解決在日常開發(fā)工作中的各類內(nèi)容,但是在特定情況仍然存在某些場景,無法滿足需求,需要自行定義相關(guān)的validator。本節(jié)將針對自定義的validator進(jìn)行介紹。
自定義的注解
這里的場景設(shè)置為進(jìn)行IP地址的驗證,通過注解的方式,讓用戶使用驗證規(guī)則。注解定義如下:
@Target({ElementType.FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = IPAddressValidator.class)
public @interface IPAddress {
? ? String message() default "{ipaddress.invalid}";
? ? Class<?>[] groups() default {};
? ? Class<? extends Payload>[] payload() default {};
}這個注解是作用在Field字段上,運行時生效,觸發(fā)的是IPAddressValidator這個驗證類。
message- 定制化的提示信息,主要是從ValidationMessages.properties里提取,也可以依據(jù)實際情況進(jìn)行定制
groups- 這里主要進(jìn)行將validator進(jìn)行分類,不同的類group中會執(zhí)行不同的validator操作
payload- 主要是針對bean的,使用不多。
然后自定義Validator,這個是真正進(jìn)行驗證的邏輯代碼:
public class IPAddressValidator implements ConstraintValidator<IPAddress, String> {
? ? @Override
? ? public boolean isValid(String value, ConstraintValidatorContext context) {
? ? ? ? Pattern pattern = compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
? ? ? ? Matcher matcher = pattern.matcher(value);
? ? ? ? try {
? ? ? ? ? ? if (!matcher.matches()) {
? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? for (int i = 1; i <= 4; i++) {
? ? ? ? ? ? ? ? ? ? int octet = Integer.valueOf(matcher.group(i));
? ? ? ? ? ? ? ? ? ? if (octet > 255) {
? ? ? ? ? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? return false;
? ? ? ? }
? ? }
}關(guān)于IP地址的驗證規(guī)則是通用的,具體邏輯不用太在意,主要是需要這里Validator這個接口,以及其中的兩個泛型參數(shù),第一個為注解名稱,第二個為實際字段的數(shù)據(jù)類型。
使用自定義的注解
定義了實體類CustomFieldBean.java
@Data
public class CustomFieldBean {
? ? @IPAddress
? ? private String ipAddr;
}使用方法非常簡約,基于注解,無侵入邏輯。
單元測試用例
測試代碼:
@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomFieldValidatorTest {
? ? @Autowired
? ? private ProductService productService;
? ? @Test(expected = ConstraintViolationException.class)
? ? public void testInvalid() {
? ? ? ? CustomFieldBean customFieldBean = new CustomFieldBean();
? ? ? ? customFieldBean.setIpAddr("1.2.33");
? ? ? ? this.productService.doCustomField(customFieldBean);
? ? }
? ? @Test
? ? public void testValid() {
? ? ? ? CustomFieldBean customFieldBean = new CustomFieldBean();
? ? ? ? customFieldBean.setIpAddr("1.2.33.123");
? ? ? ? this.productService.doCustomField(customFieldBean);
? ? }
}自定義執(zhí)行Validator
如果不希望由系統(tǒng)自行觸發(fā)Validator的驗證邏輯,則可以由開發(fā)者自行進(jìn)行驗證。在Spring Boot已經(jīng)內(nèi)置了Validator實例,直接將其加載進(jìn)來即可。
使用示例如下:
@Autowired private Validator validator;
自定義執(zhí)行的單元測試
測試代碼如下:
@RunWith(SpringRunner.class)
@SpringBootTest
public class CodeValidationTest {
? ? @Autowired
? ? private Validator validator;
? ? @Test(expected = ConstraintViolationException.class)
? ? public void testValidator() {
? ? ? ? CustomFieldBean input = new CustomFieldBean();
? ? ? ? input.setIpAddr("123.3.1");
? ? ? ? Set<ConstraintViolation<CustomFieldBean>> violations = validator.validate(input);
? ? ? ? if (!violations.isEmpty()) {
? ? ? ? ? ? throw new ConstraintViolationException(violations);
? ? ? ? }
? ? }
}自定義Validation注解
最近新開了一個項目,雖然hibernate-validator很好用,但是有時不能滿足稍微復(fù)雜一些的業(yè)務(wù)校驗。為了不在業(yè)務(wù)代碼中寫校驗邏輯,以及讓代碼更優(yōu)雅,故而采用了自定義校驗注解的方式。
場景說明
本例注解應(yīng)用場景: 填寫表單時,某一項數(shù)據(jù)存在時,對應(yīng)的一類數(shù)據(jù)都應(yīng)存在,一同提交。
源碼
1.類注解
主注解用于標(biāo)記要在校驗的實體類
@Target( { TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = RelateOtherValidator.class)
@Documented
public @interface RelateOther {
? ? String message() default "";
? ? /**
? ? ?* 校驗數(shù)量
? ? ?*/
? ? int num() default 2;
? ? Class<?>[] groups() default {};
? ? Class<? extends Payload>[] payload() default {};
}2.輔助注解
輔助注解用于標(biāo)注于要校驗的字段,isMaster區(qū)分為主注解和從注解。
主注解是關(guān)鍵字段,存在才進(jìn)行校驗從注解對應(yīng)字段的有效性;主注解的value()屬性可以設(shè)置默認(rèn)值,當(dāng)字段對應(yīng)值對應(yīng)value()時才開啟校驗。
從注解為等待校驗的值,默認(rèn)為從注解。
@Target( { FIELD })
@Retention(RUNTIME)
@Documented
public @interface RelateOtherItem {
? ? /**
? ? ?* 是否為主字段,主字段存在才進(jìn)行校驗
? ? ?*/
? ? boolean isMaster() default false;
? ? /**
? ? ?* 用于開啟對指定值校驗判斷,master字段有效
? ? ?* 當(dāng)前為master且value與標(biāo)注字段值相等才進(jìn)行校驗,
? ? ?*/
? ? String value() default "";
}3.校驗類
校驗類為實際執(zhí)行校驗邏輯的類,在類注解的@Constraint的validatedBy屬性上設(shè)置。
要設(shè)置為校驗類,首先要實現(xiàn)ConstraintValidator類的isValid方法。
@Slf4j ?// @Slf4j是lombok的注解
public class RelateOtherValidator implements ConstraintValidator<RelateOther, Object> {
?? ?// 要校驗的個數(shù)
? ? private int validateNum;
? ? @Override
? ? public void initialize(RelateOther constraintAnnotation) {
? ? ? ? validateNum = constraintAnnotation.num();
? ? }
? ? @Override
? ? public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
? ? ? ? if (o == null) {
? ? ? ? ? ? return true;
? ? ? ? }
? ? ? ? Field[] declaredFields = o.getClass().getDeclaredFields();
? ? ? ? boolean mater = false;
? ? ? ? int emptyNum = 0;
? ? ? ? try {
? ? ? ? ? ? // 總共需要校驗的字段數(shù)
? ? ? ? ? ? int totalValidateNum = validateNum;
? ? ? ? ? ? for (Field field : declaredFields) {
? ? ? ? ? ? ? ? // 校驗是否進(jìn)行過標(biāo)注
? ? ? ? ? ? ? ? if (!field.isAnnotationPresent(RelateOtherItem.class)) {
? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (validateNum > 0 && totalValidateNum-- < 0) {
? ? ? ? ? ? ? ? ? ? return false;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? field.setAccessible(true);
? ? ? ? ? ? ? ? Object property = field.get(o);
? ? ? ? ? ? ? ? RelateOtherItem relateOtherItem = field.getAnnotation(RelateOtherItem.class);
? ? ? ? ? ? ? ? // 主字段不存在,則校驗通過
? ? ? ? ? ? ? ? if (relateOtherItem.isMaster()) {
? ? ? ? ? ? ? ? ? ? if (property==null) {
? ? ? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? // 與指定值不一致,校驗通過
? ? ? ? ? ? ? ? ? ? if (!StringUtils.isEmpty(relateOtherItem.value()) && !relateOtherItem.value().equals(property)) {
? ? ? ? ? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? mater = true;
? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (null == property) {
? ? ? ? ? ? ? ? ? ? emptyNum++;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? // 主字段不存在,則校驗通過
? ? ? ? ? ? if (!mater) {
? ? ? ? ? ? ? ? log.info("RelateOther注解主字段不存在");
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? ? ? return emptyNum==0;
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? log.info("RelateOther注解,解析異常 {}", e.getMessage());
? ? ? ? ? ? return false;
? ? ? ? }
? ? }
}4.校驗失敗
注解校驗不同時會拋出一個MethodArgumentNotValidException異常。這里可以采用全局異常處理的方法,進(jìn)行捕獲處理。捕獲之后的異常可以獲取BindingResult 對象,后面就跟hibernate-validator處理方式一致了。
BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult();
5.使用demo
注解的使用類似下面,首先在請求實體類上標(biāo)注類注解,再在對應(yīng)的字段上標(biāo)注輔助注解。
@RelateOther(message = "xx必須存在!",num=2)
public class MarkReq ?{
?? ?@RelateOtherItem (isMaster= true,value="1")
?? ?private Integer ?girl;
?? ?@RelateOtherItem?
?? ?private Integer sunscreen;
?? ?private String remarks;
}總結(jié)
自定義注解在開發(fā)中還是很好用的,本文主要起到拋磚引玉的作用。對于首次使用的朋友應(yīng)該還是有些用處的。
這些僅為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用RestTemplate調(diào)用https接口跳過證書驗證
這篇文章主要介紹了使用RestTemplate調(diào)用https接口跳過證書驗證,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Mybatis逆向工程實現(xiàn)連接MySQL數(shù)據(jù)庫
本文主要介紹了Mybatis逆向工程實現(xiàn)連接MySQL數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
springboot?serviceImpl初始化注入對象實現(xiàn)方式
這篇文章主要介紹了springboot?serviceImpl初始化注入對象實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
SpringBoot整合kaptcha實現(xiàn)圖片驗證碼功能
這篇文章主要介紹了SpringBoot整合kaptcha實現(xiàn)圖片驗證碼功能,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07

