自定义校验注解框架

自定义校验注解框架

经过上一篇文章的学习大家对校验注解有了初步认识,如果没有了解上一篇文章或对注解了解不深的小朋友请认真学习!!!

本框架基于Spring容器之上写的,利用AOP反射原理实现注解验证。

注意:属性set方法上注解优先于属性名上的注解!!!

引用相关依赖

		<!--    自定义注解    -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-core</artifactId>
		</dependency>

前期准备

  • 首先得写一个总开关注解,如果@SpringBootApplication注解类上有此注解说明整个项目开启注解验证,如果没有所有注解失效。
/**
 * @Author: LailaiMonkey
 * @Description:表示该项目启用注解验证
 * @Date:Created in 2020-04-12 15:46
 * @Modified By:
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({MonkeyValidatorConfiguration.class})
public @interface EnableMonkeyValidator {
}

如图:
在这里插入图片描述

  • 第二个开关写在需要验证的service实现类上,表示此类是否需要注解验证,否则方法上校验注解失效。
/**
 * @Author: LailaiMonkey
 * @Description:表示该类中所能方法启用注解验证
 * @Date:Created in 2020-04-12 15:03
 * @Modified By:
 */
@Target({METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
public @interface MonkeyValidator {
}

在这里插入图片描述

  • 加载所有注解配置
/**
 * @Author: LailaiMonkey
 * @Description:加载所有注解
 * @Date:Created in 2020-04-12 15:27
 * @Modified By:
 */
public class MonkeyValidatorConfiguration {

    /**
     * MonkeyValidatorImpl没有@component修饰,所以无法直接在AOP中注入,通过加载bean方式注入
     *
     * @param monkeyInterface
     * @return
     */
    @Bean
    public MonkeyValidatorAOP monkeyInterfaceAOP(MonkeyValidatorImpl monkeyInterface) {
        return new MonkeyValidatorAOP(monkeyInterface);
    }

    /**
     * 可以获得继承所有分解器的注解
     *
     * @param monkeyResolvers
     * @return
     */
    @Bean
    public MonkeyValidatorImpl monkeyInterfaceImpl(List<AbstractMonkeyResolver> monkeyResolvers) {
        return new MonkeyValidatorImpl(monkeyResolvers);
    }

    /**
     * 加载所有注解
     * 如果在框架中添加自己注解需要以Bean方式注入
     * @return
     */
    @Bean
    public MonkeyEmailImpl monkeyEmail() {
        return new MonkeyEmailImpl();
    }

    @Bean
    public MonkeyMaxImpl monkeyMax() {
        return new MonkeyMaxImpl();
    }
    
    //把所有注解以@Bean方式注入进来,此处省略,见代码

}
  • AOP拦截Service实现上的注解,表示该类下的所有方法都需要验证
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-12 15:04
 * @Modified By:
 */
@Aspect
@Order
public class MonkeyValidatorAOP {

    private MonkeyValidatorImpl monkeyValidator;

    public MonkeyValidatorAOP(MonkeyValidatorImpl monkeyValidator) {
        this.monkeyValidator = monkeyValidator;
    }

    /**
     * 只拦截自定义注解
     */
    @Pointcut("@within(com.monkey.monkeyValidator.MonkeyValidator) " +
            "|| @annotation(com.monkey.monkeyValidator.MonkeyValidator)")
    private void validateObject() {

    }

    @Before("validateObject()")
    public void before(JoinPoint joinPoint) {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取方法上的注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();

        //参数名
        String[] parameterNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();

        //参数值
        Object[] parameterValues = joinPoint.getArgs();

        for (int i = 0; i < parameterValues.length; i++) {
            //获得每个参数上的注解
            Annotation[] annotations = parameterAnnotations[i];
            monkeyValidator.validator(annotations, parameterNames[i], parameterValues[i]);
        }

    }

}
  • 可以理解为校验框架的实现类,主要负责调用,需要调用哪个注解的实现类
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-12 15:34
 * @Modified By:
 */
public class MonkeyValidatorImpl {

    private final ExpressionParser expressionParser = new SpelExpressionParser();

    private Map<Class, AbstractMonkeyResolver> monkeyResolversMap = new HashMap<>();

    /**
     * 把注解和实现类对应起来
     *
     * @param monkeyResolvers
     */
    MonkeyValidatorImpl(List<AbstractMonkeyResolver> monkeyResolvers) {
        for (AbstractMonkeyResolver monkeyResolver : monkeyResolvers) {
            monkeyResolversMap.put(monkeyResolver.getHandleClass(), monkeyResolver);
        }
    }

    /**
     * @param parameterAnnotations 参数注解
     * @param parameterName        参数名
     * @param parameterValue       参数值
     */
    public void validator(Annotation[] parameterAnnotations,
                          String parameterName,
                          Object parameterValue) {
        //根据方法注解调用相关验证
        for (Annotation methodAnnotation : parameterAnnotations) {
            //验证实体类型
            if (MonkeyModel.class.isAssignableFrom(methodAnnotation.annotationType())) {
                if (parameterValue != null) {
                    //集合、数组、Map、实体
                    if (parameterValue instanceof Collection) {
                        int index = 0;
                        for (Object o : ((Collection) parameterValue)) {
                            validatorModel(o, index);
                            index++;
                        }
                    } else if (parameterValue instanceof Object[]) {
                        int index = 0;
                        for (Object o : (Object[]) parameterValue) {
                            validatorModel(o, index);
                            index++;
                        }
                    } else if (parameterValue instanceof Map) {
                        int[] index = {0};
                        ((Map) parameterValue).forEach((k, v) -> {
                            validatorModel(v, index[0]);
                            index[0]++;
                        });
                    } else {
                        validatorModel(parameterValue, null);
                    }
                }
            } else {
                //筛选自定义验证注解
                monkeyValidator(methodAnnotation, parameterName, parameterValue, null, null);
            }
        }
    }

    /**
     * 验证实体
     *
     * @param model
     */
    private void validatorModel(Object model, Integer index) {
        try {
            Field[] fields = model.getClass().getDeclaredFields();
            String modelName = model.getClass().getName();
            for (Field field : fields) {
                //字段值
                Object fieldValue = FieldUtils.getFieldValue(model, field.getName());

                //获得属性上所有注解
                Annotation[] annotations = findAnnotations(field, model.getClass());

                boolean needValidator = true;
                for (Annotation annotation : annotations) {
                    if (MonkeyValidatorOnCondition.class.isAssignableFrom(annotation.annotationType())) {
                        MonkeyValidatorOnCondition validate = (MonkeyValidatorOnCondition) annotation;
                        //获得该注解表达式
                        String value = validate.value();
                        //使用了Spring的SqEL,可以在EvaluationContext单独计算表达式的值
                        //把整个Model值放入EvaluationContext中
                        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext(model);
                        //解析表达式
                        Expression expression = expressionParser.parseExpression(value);
                        //计算指定表达式值
                        Object expressionValue = expression.getValue(standardEvaluationContext);
                        //表达式为假不需要验证
                        if (Boolean.FALSE.equals(expressionValue)) {
                            needValidator = false;
                            break;
                        }
                    }
                }

                //需要验证
                if (needValidator) {
                    for (Annotation annotation : annotations) {
                        monkeyValidator(annotation, field.getName(), fieldValue, index, modelName);
                    }
                }
            }
        } catch (Exception e) {
            throw new MonkeyValidatorException(e);
        }
    }

    @SuppressWarnings("unchecked")
    private void monkeyValidator(Annotation methodAnnotation, String parameterName,
                                 Object parameterValue, Integer index, String modelName) {
        //筛选自定义验证注解
        if (monkeyResolversMap.containsKey(methodAnnotation.annotationType())) {
            AbstractMonkeyResolver monkeyResolver = monkeyResolversMap.get(methodAnnotation.annotationType());

            boolean validator = monkeyResolver.validator(parameterValue, methodAnnotation);

            //验证失败抛出异常
            if (!validator) {
                StringBuilder errorMessage = new StringBuilder(parameterName + ":" + monkeyResolver.getMessage(methodAnnotation));
                if (!StringUtils.isEmpty(modelName)) {
                    if (index == null) {
                        errorMessage.replace(0, errorMessage.length(), modelName.substring(modelName.lastIndexOf('.') + 1) + "." + errorMessage);
                    } else {
                        errorMessage.replace(0, errorMessage.length(), modelName.substring(modelName.lastIndexOf('.') + 1) + "[" + index + "]." + errorMessage);
                    }
                }
                throw new MonkeyValidatorException(errorMessage.toString());
            }
        }
    }

    /**
     * 获得该字段上所有注解,set方法注解优先于字段注解
     *
     * @param field
     * @param clazz
     * @return
     */
    private Annotation[] findAnnotations(Field field, Class<?> clazz) {
        //获得set方法上注解
        Method method = MethodUtils.getAccessibleMethod(clazz, "set" + StringUtils.capitalize(field.getName()), field.getType());
        Annotation[] methodAnnotations = method.getDeclaredAnnotations();
        if (methodAnnotations.length > 0) {
            return methodAnnotations;
        }
        //字段上注解
        return field.getDeclaredAnnotations();
    }

  • 最后需要所有注解实现,因为注解验证返回值和消息体等。。。都是一样的,所以需要继承一个抽象类
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-12 15:23
 * @Modified By:
 */
abstract public class AbstractMonkeyResolver<T extends Annotation> {

    public abstract Class<T> getHandleClass();

    public abstract boolean validator(Object value, T annotation);

    public abstract String getMessage(T annotation);

}

自定义注解

前期配置已经准备完毕,现在开始写对应的校验注解,这里只举一种(验证字符串不为null或’’),详情见源码。
如果想要此注解生效需要在MonkeyValidatorConfiguration中把注解以Bean方式注入进来,否则不生效。

  • 开始撰写自定义注解
/**
 * @Author: LailaiMonkey
 * @Description:用于字符串不能为空或空串
 * @Date:Created in 2020-04-12 15:16
 * @Modified By:
 */
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface MonkeyNotBlank {

    /**
     * 提示信息
     * @return
     */
    String message() default "不能为空";

    /**
     * 不为Null则校验
     * @return
     */
    boolean nullable() default false;
}
  • 撰写注解实现类,就是该注解需要处理的逻辑
/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-12 15:18
 * @Modified By:
 */
public class MonkeyNotBlankImpl extends AbstractMonkeyResolver<MonkeyNotBlank> {

    @Override
    public Class<MonkeyNotBlank> getHandleClass() {
        return MonkeyNotBlank.class;
    }

    @Override
    public boolean validator(Object value, MonkeyNotBlank annotation) {
        if (value == null) {
            return annotation.nullable();
        }
        return !StringUtils.isEmpty(value.toString().trim());
    }

    @Override
    public String getMessage(MonkeyNotBlank annotation) {
        return annotation.message();
    }
}

经过一系列的准备终于写完一个验证注解,前期准备很多,不感兴趣的小伙可以直接复制过来,直接编写自定义注解即可。这里小编不带着大家运行了,大家下载源码去运行一下吧。

注解使用方法

小编的源码几乎包含项目开发中多数情况的参数验证。

  • 验证参数是集合、数组、Map、实体
    需要在参数前面加@MonkeyModel表示此参数为以上四种类型,在对接实体属性上添加相应注解即可。

接口代码如下:(接口上注解不生效,规范下我加上了)

	/**
     * ======== 下面是自定注解使用演示 =========
     **/
    void monkeyValidator(@MonkeyModel MonkeyValidatorModel model, @MonkeyNotBlank String param);

实现类代码如下:
别忘了在实现类上加@MonkeyValidator注解,否则不生效

    /**
     * ======== 下面是自定注解使用演示 =========
     **/
    @Override
    public void monkeyValidator(@MonkeyModel MonkeyValidatorModel model, @MonkeyNotBlank String param) {

    }

需要验证的Model代码如下:

/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-18 16:09
 * @Modified By:
 */
@Data
public class MonkeyValidatorModel {

    @MonkeyNotBlank
    private String name;

    @MonkeySize(min = 3, max = 6)
    private List<String> friendNames;

    @MonkeyMin(value = 18)
    @MonkeyMax(value = 60)
    private Integer age;

    @MonkeyTel
    private String tel;

    @MonkeyEmail
    private String email;
}

  • 以上是基本的使用方法还有一个特殊情况
    例如:年龄为18我才对手机号进行验证,否则不需要验证手机号

Model需要改为如下:

/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-18 16:09
 * @Modified By:
 */
@Data
public class MonkeyValidatorModel {

    @MonkeyMin(value = 18)
    @MonkeyMax(value = 60)
    private Integer age;

	@MonkeyValidatorOnCondition("age != null && age == 18")
    @MonkeyTel
    private String tel;
}

@MonkeyValidatorOnCondition只有满足表达式的值,此属性下所有注解生效,否则不生效。

分组验证

  • 那怎么进行分组验证呢?(添加需要验证,更新不需要验证)
    只需要变换一下Model的写法即可完成

Model代码如下:

/**
 * @Author: LailaiMonkey
 * @Description:
 * @Date:Created in 2020-04-18 16:09
 * @Modified By:
 */
public class MonkeyValidatorModel {

    interface Common {
        String getName();

        void setName(String name);
    }

    class Create implements Common {
        @MonkeyNotBlank
        private String name;

        @Override
        public String getName() {
            return name;
        }

        @Override
        public void setName(String name) {
            this.name = name;
        }
    }
}

参数使用MonkeyValidatorModel.Create即可实现分组验证

  • 项目目录
    自定义异常就不说了,注解会了异常也没问题了。impl是所有注解的实现。
    在这里插入图片描述

总结

需要自定义注解只需三步:

  • 撰写自定义注解
  • 撰写自定义注解实现类
  • MonkeyValidatorConfiguration配置类中以Bean方法注入该注解

优点:灵活,扩展性强、简单
缺点:如果底层校验可能会有问题

GitHub源码地址:https://github.com/Lailaimonkey/MonkeyValidator.git

CSDN源码地址:https://download.csdn.net/download/h273979586/12338106

如果有哪个注解失效或哪种情况不能验证希望小伙伴在下面留言,小编第一时间回复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值