用spring Aop 和反射,写的参数验证模块

        目前spring常用的验证框架有 JSR - 303 、Bean验证框架等等。但是这些验证框架都有一个毛病,就是使用困难,内聚性差,并且代码可控性低。

        比如JSR - 303框架注解,@Null、@NotNull 等等,太过于繁琐,并且一个实体类中每个字段都要注解验证。但是我们常用的验证可能只有空值检查,或者适合我们项目的常用验证。并且自定义验证太过于麻烦,并且报错信息控制繁琐到让人难以忍受。所以本人按照个人理解和使用习惯,写了一个自定义验证框架,盼望各位看官多多指教。

首先写一个自定义注解

//自定义注解,用于放置需要验证的controller方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BetterCheck {

    CheckGroupEumn group() default CheckGroupEumn.NORMAL;

}

public enum CheckGroupEumn {

    NORMAL(1, "普通组"),
    UPDATE(2, "更新组")
    ;
}

        这个自定义注解里面,包含了一个组枚举类,用于区分校验组,比如新建组,和修改组。这样可以应付多种情况下的检测,从而达到少写实体类的目的。

//检验注解,普通组和更新组
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NormalCheck {

    CheckTypeEumn type() default CheckTypeEumn.NOT_CHECK;
    boolean allGroup() default true;
    long max() default 100;
    long min() default 0;
}

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface UpdateCheck {

    CheckTypeEumn type() default CheckTypeEumn.NOT_CHECK;
    long max() default 100;
    long min() default 0;
}


public enum CheckTypeEumn {

    NOT_CHECK(1, "不做检测"),
    MOBILE(2, "手机"),
    MONEY(3, "金钱"),
    RANGE(4, "范围"),
    NUll(5,"空值")
    ;
}

可以看到普通组中加入了 allGroup这个 boolean变量,这是为了可以让一个注解,同时在两个组都能进行验证。并且本验证模块,和本人写这个模块的最大原因就是,空值检查自动检查。因为几乎所有变量默认都是检查空值。。。(标题吹比框架....,,容我做次标题党 O(∩_∩)O哈哈)

基本工作准备完毕,下面就开始写切面类,对方法进行增强

public class ValidaAspect {

@Pointcut("@annotation(com.myvalida.anntion.BetterCheck)")
    public void checkPoint() {
    }

@Around("checkPoint()")
    public Object arount(ProceedingJoinPoint point) throws Throwable {
        //获取当前方法
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        //获取当前注解的Group环境
        BetterCheck betterCheck = method.getAnnotation(BetterCheck.class);
        CheckGroupEumn group = betterCheck.group();
        //获取所有的参数,和所有的参数变量
        Object[] objects = point.getArgs();
        Parameter[] parameters = method.getParameters();
        //进入检验函数
        check(objects, parameters, group);
        return point.proceed(objects);
    }
}
//首先对所有参数和变量逐个检查,检查函数用迭代,目前只嵌套2次
private void check(Object[] objs, Parameter[] paras, CheckGroupEumn group) throws Throwable {
        for (int i = 0; i < objs.length; i++) {
            Parameter para = paras[i];
            Object obj = objs[i];
            Annotation[] annotations = para.getAnnotations();
            String paraName = para.getName();
            doCheck(paraName, obj, annotations, group, 2);
        }
    }

//doCheck函数
private void doCheck(String paraName, Object obj, Annotation[] annotations, CheckGroupEumn group, int floor) throws Throwable {
        if (annotations == null || annotations.length == 0) {
            checkNotNull(paraName, obj, group, floor);
        } else {
            for (Annotation annotation : annotations) {
                NormalCheck checkType = checkGroup(group, annotation);
                if (checkType != null) {
                    switch (checkType.type()) {
                        case NOT_CHECK:
                            break;
                        case MONEY:
                            checkMoney(obj, checkType.type().getLabel());
                            break;
                        。。。。。。。。
                    }
                } else {
                    checkNotNull(paraName, obj, group, 1);
                }
            }
        }
    }

当变量没有注解时候,就默认做空值检查。

 private void checkNotNull(String paraName, Object obj, CheckGroupEumn group, int floor) throws Throwable {
        if (ObjectUtils.isEmpty(obj)) {
            throw new OperationNotAllowException(paraName + "值不能为空");
        } else if (floor > 1) {
            Class clazz = obj.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Annotation[] annotations = field.getAnnotations();
                Object o = field.get(obj);
                doCheck(field.getName(), o, annotations, group, floor - 1);
            }
        }
    }

当对象不为空的时候,对实体类内部所有变量进行检查。只重复一次。

//检查分组功能,默认NormalCheck就包含了两个组的检测。
private NormalCheck checkGroup(CheckGroupEumn group, Annotation annotation) {
        if (group.equals(CheckGroupEumn.NORMAL)) {
            if (annotation instanceof NormalCheck) {
                return (NormalCheck) annotation;
            }
        } else {
            if (annotation instanceof UpdateCheck) {
                return modelMapper.map(annotation, NormalCheck.class);
            } else if (annotation instanceof NormalCheck && ((NormalCheck) annotation).allGroup()) {
                return (NormalCheck) annotation;
            }
        }
        return null;
    }

 

检测方法举例

private void checkNull(String paraName, Object obj) throws Throwable {
        if (!ObjectUtils.isEmpty(obj)) {
            throw new OperationNotAllowException("不能传入" + paraName + ",值必须为空");
        }
    }


聊聊亮点:

1.  内聚性强,所有的检测方法都在一个类,有复杂方法,可以引入实现类。

2.  不用记多个注解,只要三个注解  @BetterCheck   @NormalCheck   @UpdateCheck

3.  异常可控性强,想怎么玩就怎么玩

4.  代码量少,如果默认非空检测,只要一个@BetterCheck,不会影响原来任何代码

5.  注解更清晰,枚举类说明,清晰介绍检查是干啥子的

6.  性能更强,少了反射调用方法的代码,性能更上n层楼

7.  轻量级结构,仅仅依赖于Spring

使用示范:

@ApiOperation(value = "添加组")
    @BetterCheck
    @GetMapping("testadd")
    public void testAdd(
            TestClass cmd
            , @NormalCheck(type = CheckTypeEumn.RANGE, max = 13L, min = 11L)                               
              Integer size
            , String normal
    ) {
        System.out.println("因为有了无奈");
    }

    @ApiOperation(value = "更新组")
    @BetterCheck(group = CheckGroupEumn.UPDATE)
    @GetMapping("testUpdate")
    public void testUpdate(
            TestClass cmd
            , @UpdateCheck(type = CheckTypeEumn.NUll) String mustNull
            , String normal
    ) {
        System.out.println("这就是人生");
    }


public class TestClass {

    private String defaultCheck;

    @NormalCheck(type = CheckTypeEumn.NUll,allGroup = false)
    //UpdateCheck组中默认做空值检测
    private Long id;

    @NormalCheck(type = CheckTypeEumn.RANGE )
    private Long range;

    @NormalCheck(type = CheckTypeEumn.MOBILE)
    private String mobile;

    @NormalCheck(type = CheckTypeEumn.MONEY)
    private BigDecimal money;

    @NormalCheck
    private String notCheck;
}

两个组,添加组和更新组,个人感觉还是比较清晰,简洁。

 

 

你可能感兴趣的:(java)