本次發表文章距上次發表已近有兩月有餘,緣由是兩月前離開了上家公司(離開緣由可能會在年終終結敘述,本篇暫且忽略),來到了如今所在的京東集團,須要花時間熟悉環境和沉澱一下新的東西,所以寫文章也暫時沒那麼勤奮了,不得不說此次是機遇也是對本身職業生涯的一次重要決定。java
話說本篇內容主要分享的是自定義方法參數的驗證,參數的基本校驗在對外接口或者公用方法時常常所見,用過hibernate的驗證方式的朋友必定不會陌生,讀完本篇內容可以很好的幫助各位朋友對自定義參數驗證方式有必定了解:spring
對於自定義參數驗證來講,須要注意的步驟有如下幾步:springboot
根據上面思路描述,咱們首先須要有註解來標記哪些實體屬性須要作不一樣的校驗,所以這裏建立兩種校驗註解(爲了本章簡短性):IsNotBlank(校驗不能爲空)和RegExp(正則匹配校驗),以下代碼:app
1 @Documented 2 @Retention(RetentionPolicy.RUNTIME) 3 @Target(ElementType.FIELD) 4 public @interface IsNotBlank { 5 String des() default ""; 6 }
1 @Documented 2 @Retention(RetentionPolicy.RUNTIME) 3 @Target(ElementType.FIELD) 4 public @interface RegExp { 5 String pattern(); 6 7 String des() default ""; 8 }
而後爲了統一這裏建立公用的驗證方法,此方法須要傳遞待驗證參數的具體實例,其主要作的工做有:框架
這裏有以下公用的驗證方法:spring-boot
1 public class ValidateUtils { 2 3 public static void validate(Object object) throws IllegalAccessException { 4 if (object == null) { 5 throw new NullPointerException("數據格式校驗對象不能爲空"); 6 } 7 //獲取屬性列 8 Field[] fields = object.getClass().getDeclaredFields(); 9 for (Field field : fields) { 10 //過濾無驗證註解的屬性 11 if (field.getAnnotations() == null || field.getAnnotations().length <= 0) { 12 continue; 13 } 14 //容許private屬性被訪問 15 field.setAccessible(true); 16 Object val = field.get(object); 17 String strVal = String.valueOf(val); 18 19 //具體驗證 20 validField(field, strVal); 21 } 22 } 23 24 /** 25 * 具體驗證 26 * 27 * @param field 屬性列 28 * @param strVal 屬性值 29 */ 30 private static void validField(Field field, String strVal) { 31 if (field.isAnnotationPresent(IsNotBlank.class)) { 32 validIsNotBlank(field, strVal); 33 } 34 if (field.isAnnotationPresent(RegExp.class)) { 35 validRegExp(field, strVal); 36 } 37 /** add... **/ 38 } 39 40 /** 41 * 匹配正則 42 * 43 * @param field 44 * @param strVal 45 */ 46 private static void validRegExp(Field field, String strVal) { 47 RegExp regExp = field.getAnnotation(RegExp.class); 48 if (Strings.isNotBlank(regExp.pattern())) { 49 if (Pattern.matches(regExp.pattern(), strVal)) { 50 return; 51 } 52 String des = regExp.des(); 53 if (Strings.isBlank(des)) { 54 des = field.getName() + "格式不正確"; 55 } 56 throw new IllegalArgumentException(des); 57 } 58 } 59 60 /** 61 * 非空判斷 62 * 63 * @param field 64 * @param val 65 */ 66 private static void validIsNotBlank(Field field, String val) { 67 IsNotBlank isNotBlank = field.getAnnotation(IsNotBlank.class); 68 if (val == null || Strings.isBlank(val)) { 69 String des = isNotBlank.des(); 70 if (Strings.isBlank(des)) { 71 des = field.getName() + "不能爲空"; 72 } 73 throw new IllegalArgumentException(des); 74 } 75 } 76 }
有了具體驗證方法,咱們須要個測試實例,以下測試接口和實體:測試
1 public class TestRq extends BaseRq implements Serializable { 2 3 @IsNotBlank(des = "暱稱不能爲空") 4 private String nickName; 5 @RegExp(pattern = "\\d{10,20}", des = "編號必須是數字") 6 private String number; 7 private String des; 8 private String remark; 9 }
1 @PostMapping("/send") 2 public BaseRp<TestRp> send(@RequestBody TestRq rq) throws IllegalAccessException { 3 ValidateUtils.validate(rq); 4 return testService.sendTestMsg(rq); 5 }
上面是圍繞公用驗證方法來寫的,一般實際場景中都把它和aop結合來作統一驗證;來定製兩個註解,MethodValid方法註解(是否驗證全部參數)和ParamValid參數註解(標記方法上的某個參數):spa
1 @Documented 2 @Retention(RetentionPolicy.RUNTIME) 3 @Target(value = {ElementType.METHOD}) 4 public @interface MethodValid { 5 /** 6 * 驗證全部參數 7 * 8 * @return true 9 */ 10 boolean isValidParams() default true; 11 }
1 @Documented 2 @Retention(RetentionPolicy.RUNTIME) 3 @Target(value = {ElementType.PARAMETER}) 4 public @interface ParamValid { 5 }
有了兩個標記註解再來建立aop,我這裏是基於springboot框架的實例,全部引入以下mvn:hibernate
1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-aop</artifactId> 4 </dependency>
而後aop須要作以下邏輯:code
這裏特殊的地方是,想要獲取方法參數對應的註解,須要method.getParameterAnnotations()獲取全部全部參數註解後,再用索引來取參數對應的註解;以下aop代碼:
1 package com.shenniu003.common.validates; 2 3 import com.shenniu003.common.validates.annotation.MethodValid; 4 import com.shenniu003.common.validates.annotation.ParamValid; 5 import org.aspectj.lang.ProceedingJoinPoint; 6 import org.aspectj.lang.annotation.Around; 7 import org.aspectj.lang.annotation.Aspect; 8 import org.aspectj.lang.reflect.MethodSignature; 9 import org.springframework.stereotype.Component; 10 11 import java.lang.annotation.Annotation; 12 import java.lang.reflect.Method; 13 import java.util.Arrays; 14 15 /** 16 * des: 17 * 18 * @author: shenniu003 19 * @date: 2019/12/01 11:04 20 */ 21 @Aspect 22 @Component 23 public class ParamAspect { 24 25 @Around(value = "@annotation(methodValid)", argNames = "joinPoint,methodValid") 26 public Object validMethod(ProceedingJoinPoint joinPoint, MethodValid methodValid) throws Throwable { 27 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); 28 Method method = methodSignature.getMethod(); 29 System.out.println("method:" + method.getName()); 30 String strArgs = Arrays.toString(joinPoint.getArgs()); 31 System.out.println("params:" + strArgs); 32 33 //獲取方法全部參數的註解 34 Annotation[][] parametersAnnotations = method.getParameterAnnotations(); 35 36 for (int i = 0; i < joinPoint.getArgs().length; i++) { 37 Object arg = joinPoint.getArgs()[i]; 38 if (arg == null) { 39 continue; // 40 } 41 42 if (methodValid.isValidParams()) { 43 //驗證全部參數 44 System.out.println(arg.getClass().getName() + ":" + arg.toString()); 45 ValidateUtils.validate(arg); 46 } else { 47 //只驗證參數前帶有ParamValid註解的參數 48 //獲取當前參數全部註解 49 Annotation[] parameterAnnotations = parametersAnnotations[i]; 50 //是否匹配參數校驗註解 51 if (matchParamAnnotation(parameterAnnotations)) { 52 System.out.println(Arrays.toString(parameterAnnotations) + " " + arg.getClass().getName() + ":" + arg.toString()); 53 ValidateUtils.validate(arg); 54 } 55 } 56 } 57 return joinPoint.proceed(); 58 } 59 60 /** 61 * 是否匹配參數的註解 62 * 63 * @param parameterAnnotations 參數對應的全部註解 64 * @return 是否包含目標註解 65 */ 66 private boolean matchParamAnnotation(Annotation[] parameterAnnotations) { 67 boolean isMatch = false; 68 for (Annotation parameterAnnotation : parameterAnnotations) { 69 if (ParamValid.class == parameterAnnotation.annotationType()) { 70 isMatch = true; 71 break; 72 } 73 } 74 return isMatch; 75 } 76 }
這裏編寫3中方式的測試用例,驗證方法全部參數、無參數不驗證、驗證方法參數帶有@ParamValid的參數,以此達到不一樣需求參數的校驗方式:
1 //驗證方法全部參數 2 @MethodValid 3 public void x(TestRq param1, String param2) { 4 } 5 //無參數不驗證 6 @MethodValid 7 public void xx() { 8 } 9 //驗證方法參數帶有@ParamValid的參數 10 @MethodValid(isValidParams = false) 11 public void xxx(TestRq param1, @ParamValid String param2) { 12 }
一樣用send接口做爲測試入口,調用上面3種方法:
1 @PostMapping("/send") 2 @MethodValid 3 public BaseRp<TestRp> send(@RequestBody TestRq rq) throws IllegalAccessException { 4 // ValidateUtils.validate(rq); 5 testController.x(rq, "驗證方法全部參數"); 6 testController.xx(); 7 testController.xxx(rq, "驗證方法參數帶有@ParamValid的參數"); 8 return testService.sendTestMsg(rq); 9 }