• AOP+反射 批量参数校验


    • 本案例包括4个核心类:
      AopUtils:抽取出来的公共方法
      @ValidateGroup
      @ValidateField
      ValidateAspectJHandler
    • 功能包括:长度、值范围、正则匹配、非空校验
    • 以下是设计思路、最终代码、测试结果
    • 后续扩展只需要修改@ValidateField 和 ValidateAspectJHandler

    1.演示: 最终使用方法

    以注册功能为例

    @RestController
    @RequestMapping("validater")
    public class ValidateController {
    
      @ValidateGroup(fields = {
          @ValidateField(index = 0,notNull = true,maxLen = 10,code = "param1-error",message = "param1校验错误"),
          @ValidateField(index = 1,notNull = true,fieldName = "passWord",minLen = 6,code = "passWord-erro",message = "密码校验错误"),
          @ValidateField(index = 1,notNull = true,fieldName = "age",minVal = 0,code = "age-error",message = "年龄不能小于0"),
          @ValidateField(index = 1,notNull = true,fieldName = "tall",minVal = 0,maxVal = 250.9,code ="tall-error",message = "身高范围出错"),
          @ValidateField(index = 1,notNull = true,fieldName = "phone",regStr = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$",code = "phone-error",message = "手机号错误")
      })
      @PostMapping("post")
      public String postValidater(@RequestParam String param1,  RegisterDto dto){
    
        System.out.println("成功通过校验");
        System.out.println("第一个参数是:" + param1);
    
        System.out.println("第二个参数是"+dto.toString());
    
        return "succeed";
      }	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    其中:请求Dto包含name、passWord、phone等字段;
    利用AspectJ对接口方法直接进行代理校验

    2.流程分析

    • AspectJ代理注解@ValidateGroup标注的方法,而这个Group注解中的属性就是@ValidateField []
    • 获取到@ValidateField数组后,遍历。通过比对注解的参数 与 dto或者param中对应名字的参数来进行校验
    • 如果校验成功就放行,校验失败就抛异常终止

    3.公共方法抽取AopUtils

    经过流程分析可知:至少需要以下几个方法(并可以抽取为公共组件)

    • public Method getMethod(ProceedingJoinPoint pjp):获取被AOP拦截的方法Method对象
    • public Annotation getAnnotationByMethod(Method method, Class annoClass):获取目标方法对象的指定注解对象
    • public Object getFieldFromDtoByFieldName(Object dto , String fieldName) 从dto中,获取指定属性名的属性值

    如下工具类也可以做成全静态方法

    public class AopUtils {
    
    private volatile static AopUtils aopUtils;
    
    
    private AopUtils() {
    }
    
    public static AopUtils getInstance() {
        if (aopUtils == null) {
            synchronized (AopUtils.class) {
                if (aopUtils == null) {
                    aopUtils = new AopUtils();
                }
            }
        }
        return aopUtils;
    }
    
    /**
     * 获取目标类的指定方法
     */
    public Method getMethodByClassAndName(Class c, String methodName) {
        Method[] methods = c.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        return null;
    }
    
    /**
     * 获取目标方法的指定注解
     * 相当于 method.getAnnotation(xxxx.class);
     */
    public Annotation getAnnotationByMethod(Method method, Class annoClass) {
        Annotation all[] = method.getAnnotations();
        for (Annotation annotation : all) {
            if (annotation.annotationType() == annoClass) {
                return annotation;
            }
        }
        return null;
    }
    
    /**
     * 获取被拦截方法的对象
     * 配合使用,最终用于在Aspectj中获取被拦截方法上的注解
     * 例如:AopUtils.getMethod(pjp).getDeclaredAnnotation(被aop拦截的注解.class)
     */
    public Method getMethod(ProceedingJoinPoint pjp) {
        //获取参数的签名
        MethodSignature msig = (MethodSignature) pjp.getSignature();
        // MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 如果在实现类的方法上,应该使用反射获取当前对象的方法对象
        Object target = pjp.getTarget();//获取连接点所在的目标对象(被代理的对象)而不是父类or接口
        //方法名 + 方法形参 ————》获取指定的方法对象(重载)
        String methodName = msig.getName();
        Class[] parameterTypes = msig.getParameterTypes();
        Method method = null;
        try {
            method = target.getClass().getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            //log.error(...);
        }
        return method;
    }
    
    /**
     * 从dto中,获取指定属性名的属性值;
     */
    public Object getFieldFromDtoByFieldName(Object dto , String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Class dtoClazz = dto.getClass();
        Field field = dtoClazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(dto);
    }
    
    //  这个其实还有另一种写法
    //  private Method getMethod(ProceedingJoinPoint joinPoint) {
    //    try {
    //      Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
    //      return joinPoint.getTarget().getClass().getMethod(joinPoint.getSignature().getName(), parameterTypes);
    //    } catch (NoSuchMethodException e) {
    //      e.printStackTrace();
    //    }
    //    return null;
    //  }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    4.注解及设计原理

    4.1ValidateGroup

    这个注解用于被AspectJ拦截,其属性是一个数组,用于参数校验

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD})
    public @interface ValidateGroup {
    
      ValidateFiled[] fields();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.2ValidateField

    • 当且仅当只有一个参数的时候可以不用指定index
    • index默认为0,例如public void register(@RequestParam String param1 , @RequestBody Dto dto){}中应该设置index = 1 ,这是由于joinPoint.getArgs()获取的形参是一个数组,需要用index指定位置
    • 所有参数都有默认值(不进行校验)

    如下:

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.FIELD})
    public @interface ValidateField {
    
      /**
       * 参数索引位置:接口中有多个参数时用index指定需要校验的参数
       * 默认:0号索引,当且仅当接口方法只有一个参数
       */
      int index() default 0;
    
      /**
       * 默认:如果参数是基本数据类型或String,就不用指定该参数
       * 如果参数是对象,要验证对象里面某个属性,就用该参数指定属性名
       */
      String fieldName() default "";
    
      /**
       * 错误码,用于日志记录
       */
      String code() default "";
    
      /**
       * 错误提示语,用于日志记录
       */
      String message() default "";
    
      /**
       * 正则验证
       */
      String regStr() default "";
    
      /**
       * 非空校验,为true表示不能为空,false表示能够为空
       */
      boolean notNull() default false;
    
      /**
       * 字符串最大长度
       */
      int maxLen() default 0x7fffffff;
    
      /**
       * 字符串最小长度
       */
      int minLen() default 0;
    
      /**
       * 最大值,用于验证数值类型数据
       */
      double maxVal() default 0x1.fffffffffffffP+1023;
    
      /**
       * 最小值,用于验证数值类型数据
       */
      double minVal() default 0x0.0000000000001P-1022;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    5.ValidateAspectJHandler

    5.1大体结构

    @Component
    @Aspect
    public class ValidateAspectHandler {
    
      private AopUtils aopUtils = AopUtils.getInstance();
      /**
       * 使用AOP对使用了ValidateGroup的方法进行代理校验
       */
      @Around("@annotation(ValidateGroup)")
      public Object validateAround(ProceedingJoinPoint joinPoint) throws Throwable {
    //1.获取当前方法对象method
    //2.根据method对象获取对应的ValidateGroup对象
    //3.调用封装方法,将validateGroup.fields() 与 joinPoint.getArgs()形参数组传入,进行校验
    //4.如果为true,则执行下一步
        return joinPoint.proceed();
      }
    
    
      /**
       * 封装方法:验证参数是否合法
       */
      private boolean validateField(ValidateFiled[] validateFields, Object[] args) {
        for (ValidateFiled validateFiled : validateFields) {
    		//1.每次循环都是一个校验逻辑
    		//2.index仍然是指定对第几号元素进行校验
    		//3.fieldName如果不指定,那就是对基本数据类型即@RequestParam进行校验;如果指定,则对Dto即@RequestBody进行校验
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    5.2具体实现

    @Component
    @Aspect
    public class ValidateAspctJHandler {
    
    //    private static org.slf4j.Logger logger = LoggerFactory.getLogger("ValidateAspctJHandler");
        private AopUtils aopUtils = AopUtils.getInstance();
    
      /**
       * 使用AOP对使用了ValidateGroup的方法进行代理校验
       */
      //相当于用@Around + return joinPoint.proceed(); 
      //使用@Before的时候不能用ProceedingJoinPoint 只能用JoinPoint
        @Before("@annotation(ValidateGroup)")
        public void validateAround(JoinPoint joinPoint) throws Throwable {
            //获取被代理的方法对象
            Method method = aopUtils.getMethod(joinPoint);
            //获取被代理的方法对象对应的@ValidateGroup对象
            ValidateGroup validateGroup = (ValidateGroup)aopUtils.getAnnotationByMethod(method, ValidateGroup.class);
            //获取被代理方法的参数数组(这是参数值,而不是 method.getParameterTypes()返回的是Class[]  )
            Object[] args = joinPoint.getArgs();
            /*
             * args和validateGroup中包含了全部需要校验的信息,因此可以封装为一个方法
             * 在这个方法中,如果校验失败则用throws抛异常的方式终止
             */
            validateAllFields(validateGroup.fields(), args);
        }
    
        /**
         * 验证参数是否合法
         * ValidateField[]中每一条都是一个校验规则,每一条都对应一个属性
         * Object[] args中是所有的请求参数值,需要从args[validateFiled.index()中确定是对谁进行校验
         */
      private void validateAllFields(ValidateField[] validateFields, Object[] args) throws NoSuchFieldException, IllegalAccessException {
          //遍历:对每个@ValidateField进行校验
        for (ValidateField validateFiled : validateFields) {
          Object arg;
          //1.当fieldName为默认值""的时候,此时是对@RequestParam即基本数据类型orString进行校验
          if ("".equals(validateFiled.fieldName())) {
              //arg是基本数据类型orString
            arg = args[validateFiled.index()];
            //2.如果fieldName设置了,那就是对dto中的某个属性进行校验
          } else {
              //获取第index号参数dto指定的属性值
            arg = aopUtils.getFieldFromDtoByFieldName(args[validateFiled.index()], validateFiled.fieldName());
          }
    
          //3.以下是校验流程,需要同时考虑是对dto属性or基本数据类型orString
          //3.1判断参数是否为空
          if (validateFiled.notNull()) {
            if (arg == null || arg.equals("")) {
    //            logger.error(validateFiled.code() + ":" + validateFiled.message());
              throw new RuntimeException(validateFiled.code()  + ":" + validateFiled.message());
            //如果该参数能够为空,并且当参数为空时,就不用判断后面的了 ,直接返回
            } }else {
            if (arg == null || arg.equals("")) {
              return;
            }
          }
    
          //3.2判断字符串最大长度  如果设置为一个负数则不校验  默认为最大int值
          if (validateFiled.maxLen() >= 0) {
            if (arg.toString().length() > validateFiled.maxLen()) {
    //          logger.error(validateFiled.code() + ":" + validateFiled.message());
              throw new RuntimeException(validateFiled.code()  + ":" + validateFiled.message());
            }
          }
          //3.3判断字符串最小长度  如果设置为一个负数则不校验  默认为0
          if (validateFiled.minLen() >= 0) {
            if (arg.toString().length() < validateFiled.minLen()) {
    //          logger.error(validateFiled.code() + ":" + validateFiled.message());
              throw new RuntimeException(validateFiled.code()  + ":" + validateFiled.message());
            }
          }
          //3.4判断数值最大值  当不是默认值0x1.fffffffffffffP+1023的时候进行判断
          if (validateFiled.maxVal() != 0x1.fffffffffffffP+1023) {
            if (Double.parseDouble(arg.toString()) > validateFiled.maxVal()) {
    //          logger.error(validateFiled.code() + ":" + validateFiled.message());
              throw new RuntimeException(validateFiled.code()  + ":" + validateFiled.message());
            }
          }
          //3.5判断数值最小值   当不是默认值0x0.0000000000001P-1022的时候进行判断
          if (validateFiled.minVal() != 0x0.0000000000001P-1022) {
            if (Double.parseDouble(arg.toString()) < validateFiled.minVal()) {
    //          logger.error(validateFiled.code() + ":" + validateFiled.message());
              throw new RuntimeException(validateFiled.code()  + ":" + validateFiled.message());
            }
          }
    
          //3.6判断正则 若未设置正则校验则跳过
          if (!"".equals(validateFiled.regStr())) {
            if (arg instanceof String || arg instanceof Integer || arg instanceof BigDecimal || arg instanceof Double) {
              if (!(arg.toString()).matches(validateFiled.regStr())) {
    //            logger.error(validateFiled.code() + ":" + validateFiled.message());
                throw new RuntimeException(validateFiled.code()  + ":" + validateFiled.message());
              }
            }
          }
        }
        return;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    5.3接口测试

    @RestController
    @RequestMapping("validater")
    public class ValidateController {
    
      @ValidateGroup(fields = {
      	//如果是index=0也可以省略不写
          @ValidateField(index = 0,notNull = true,maxLen = 10,code = "param1-error",message = "param1校验错误"),
          @ValidateField(index = 1,notNull = true,fieldName = "passWord",minLen = 6,code = "passWord-erro",message = "密码校验错误"),
          @ValidateField(index = 1,notNull = true,fieldName = "age",minVal = 0,code = "age-error",message = "年龄不能小于0"),
          @ValidateField(index = 1,notNull = true,fieldName = "tall",minVal = 0,maxVal = 250.9,code ="tall-error",message = "身高范围出错"),
          @ValidateField(index = 1,notNull = true,fieldName = "phone",regStr = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$",code = "phone-error",message = "手机号错误")
      })
      @PostMapping("post")
      public String postValidater(@RequestParam String param1,  RegisterDto dto){
    
        System.out.println("成功通过校验");
        System.out.println("第一个参数是:" + param1);
    
        System.out.println("第二个参数是"+dto.toString());
    
        return "succeed";
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23


    如果参数错误,则会直接抛异常终止请求

    如果参数都正确就可以通过校验,如下:

  • 相关阅读:
    Ubuntu-Server-22.04安装桌面+VNC
    电子企业MES管理系统实施的功能和流程有哪些
    观点:灵魂绑定NFT和去中心化社会
    EasyPoi——导出导入表格数据工具
    Oracle/PLSQL: Ln Function
    halcon脚本-机器学习【OCR】【附源码】
    跟我学C++中级篇——右值引用和万能引用
    C#学习笔记--逻辑语句(分支和循环)
    Linux系统编程(三)——Linux下的进程
    Flask框架:如何运用Ajax轮询动态绘图
  • 原文地址:https://blog.csdn.net/m0_56079407/article/details/126317362