写自定义参数验证方式

本次发表文章距上次发表已近有两月有余,缘由是两月前离开了上家公司(离开缘由可能会在年终终结叙述,本篇暂且忽略),来到了如今所在的京东集团,须要花时间熟悉环境和沉淀一下新的东西,所以写文章也暂时没那么勤奋了,不得不说此次是机遇也是对本身职业生涯的一次重要决定。java

话说本篇内容主要分享的是自定义方法参数的验证,参数的基本校验在对外接口或者公用方法时常常所见,用过hibernate的验证方式的朋友必定不会陌生,读完本篇内容可以很好的帮助各位朋友对自定义参数验证方式有必定了解:spring

  • 自定义参数验证的思路
  • 实战参数验证的公用方法
  • aop结合方法参数验证明例

自定义参数验证的思路

对于自定义参数验证来讲,须要注意的步骤有如下几步:springboot

  1. 怎么区分须要验证的参数,或者说参数实体类中须要验证的属性(答案:可用注解标记)
  2. 对于参数要验证哪几种数据格式(如:非空、邮箱、电话以及是否知足正则等格式)
  3. 怎么获取要验证的参数数据(如:怎么获取方法参数实体传递进来的数据)
  4. 验证失败时提示的错误信息描述(如:统一默认校验错误信息,或者获取根据标记验证注解传递的错误提示文字暴露出去)
  5. 在哪一步作校验(如:进入方法内部时校验,或是能够用aop方式统一校验位置)

实战参数验证的公用方法

根据上面思路描述,咱们首先须要有注解来标记哪些实体属性须要作不一样的校验,所以这里建立两种校验注解(为了本章简短性):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 }

而后为了统一这里建立公用的验证方法,此方法须要传递待验证参数的具体实例,其主要作的工做有:框架

  1. 经过传递进来的参数获取该参数实体的属性
  2. 设置field.setAccessible(true)容许获取对应属性传进来的数据
  3. 根据对应标记属性注解来验证获取的数据格式,格式验证失败直接提示des描述

这里有以下公用的验证方法: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结合方法参数验证明例

上面是围绕公用验证方法来写的,一般实际场景中都把它和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

  1. 获取方法上传递参数(param1,param2...)
  2. 遍历每一个参数实体,若有验证注解就作校验
  3. 遍历标记有ParamValid注解的参数,若有验证注解就作校验

这里特殊的地方是,想要获取方法参数对应的注解,须要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     }

相关文章
相关标签/搜索