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字段上,運(yùn)行時生效,觸發(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-10Mybatis逆向工程實現(xiàn)連接MySQL數(shù)據(jù)庫
本文主要介紹了Mybatis逆向工程實現(xiàn)連接MySQL數(shù)據(jù)庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06springboot?serviceImpl初始化注入對象實現(xiàn)方式
這篇文章主要介紹了springboot?serviceImpl初始化注入對象實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05SpringBoot整合kaptcha實現(xiàn)圖片驗證碼功能
這篇文章主要介紹了SpringBoot整合kaptcha實現(xiàn)圖片驗證碼功能,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-07-07