美文网首页java基础
接口参数校验之JSR303+AOP

接口参数校验之JSR303+AOP

作者: 汪先森出版社 | 来源:发表于2018-12-24 17:34 被阅读117次

    前言

    参数校验,相信每个后端开发人员都有接触。一般情况下前后端都会对数据进行双重校验,保证正确性。比如某个参数不可为null,手机号格式不对,等等。
    本文的重点在接口级别校验,起始于工作中的微服务接口参数检查。大脑最省力原则告诉我,能不一个一个手动写,咱就坚持抽象出来。
    通常采用的是JSR303规范来做校验,Hibernate validator是JSR303规范的一种很好的实现。

    依赖引入(Maven)

    略(......)见谅

    接口返回

    抽象,那么执行接口方法之前要check参数,不符合场景直接返回结果。首先,我们抽象一个接口返回包装类。相信这个已经不稀奇,即使没有参数校验,大家N年前就已经做了。

    public class MessageReturn<T> {
    
        /** 状态:默认0 成功;其他为失败 */
        private int status;
        /** 响应结果*/
        private T result;
        /** 相应描述*/
        private String message;
    
        // 余下部分已省略
    }
    

    自定义注解

    AOP切点

    /**
     * 参数校验注解类
     *
     * @author  wangzhuhua
     **/
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Order(AspectOrderConstant.Validator)
    @Documented
    @Inherited
    public @interface HValidate {
    
        /**默认错误码*/
        // 这个default 是上面的MessageReturn
        int errorCode() default MessageUtil.BUSY;
    }
    

    校验分组

    这里自定义了分组校验规则

    /**
     * 参数校验分组注解类
     *
     * @author  wangzhuhua
     **/
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    @Constraint(validatedBy = HValidateGroup.HValidateGroupValidator.class)
    @Documented
    public @interface HValidateGroup {
    
        //默认错误消息
        String message() default "";
    
        //分组
        Class<?>[] groups() default { };
    
        //负载
        Class<? extends Payload>[] payload() default { };
    
        //校验分组
        Class<?>[] value() default {};
    
        class HValidateGroupValidator implements ConstraintValidator<HValidateGroup, Object> {
    
            private Class<?>[] groups;
    
            private ValidatorFactory validatorFactory;
    
            public void initialize(HValidateGroup hValidateGroup) {
                this.groups = hValidateGroup.value();
    
                validatorFactory = ValidatorInterceptor.ValidatorFactory;
            }
    
            public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
                if (object == null) {
                    return true;
                }
    
                Set<ConstraintViolation<Object>> validResult = validatorFactory.getValidator().validate(object, groups);
                if(validResult.size() == 0){
                    return true;
                }
    
                Iterator<ConstraintViolation<Object>> iterator = validResult.iterator();
                while(iterator.hasNext()){
                    ConstraintViolation violation = iterator.next();
                    constraintValidatorContext.disableDefaultConstraintViolation();
                    constraintValidatorContext.buildConstraintViolationWithTemplate(violation.getMessageTemplate())
                            .addPropertyNode(violation.getPropertyPath().toString())
                            .addConstraintViolation();
                }
    
                return false;
            }
        }
    }
    

    AOP拦截校验

    /**
     * 参数校验配置
     *
     * @author wangzhuhua
     **/
    @Component
    @Aspect
    public class ValidatorInterceptor {
    
        /**
         * true:快速校验模式,false:全部校验模式
         */
        @Value("${hibernate.validator.failFast:false}")
        private boolean validateModel;
        /**
         * 解决组合切点参数注入只适用于切点级别高的效应
         */
        ThreadLocal<HValidate> currentAnnatation = new ThreadLocal<>();
        /**
         * 校验器工厂
         */
        private ValidatorFactory validatorFactory;
    
        static ValidatorFactory ValidatorFactory;
    
        /**
         * 构建校验工厂
         */
        @PostConstruct
        public void initValidator() {
            validatorFactory = Validation.byProvider(HibernateValidator.class)
                    .configure()
    //                .messageInterpolator(new ResourceBundleMessageInterpolator(new PlatformResourceBundleLocator("MyMessages")))
                    .failFast(validateModel)
                    .buildValidatorFactory();
    
            ValidatorInterceptor.ValidatorFactory = validatorFactory;
        }
    
        /**
         * 切点
         */
        @Pointcut("@within(com.sstc.hmis.util.validator.HValidate)")
        public void pointcutWithin() {
        }
    
        /**
         * 切点
         */
        @Pointcut("@annotation(com.sstc.hmis.util.validator.HValidate)")
        public void pointcutAnnotation() {
        }
    
        /**
         *
         * @param proceedingJoinPoint
         * @param hValidate
         * @return
         */
        @Around(value = "pointcutWithin() && @within(hValidate)")
        public Object pointcutWithin(ProceedingJoinPoint proceedingJoinPoint, HValidate hValidate) throws Throwable {
            setCurrentAnnatation(hValidate);
            return process(proceedingJoinPoint);
        }
    
        /**
         *
         * @param proceedingJoinPoint
         * @param hValidate
         * @return
         */
        @Around(value = "pointcutAnnotation() && @annotation(hValidate)")
        public Object pointcutAnnotation(ProceedingJoinPoint proceedingJoinPoint, HValidate hValidate) throws Throwable {
            setCurrentAnnatation(hValidate);
            return process(proceedingJoinPoint);
        }
    
        /**
         * 校验切点
         *
         * @param proceedingJoinPoint 切点对象
         * @return 接口响应
         * @throws Throwable
         */
        @Around("pointcutWithin()")
        public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            // 获得的方法
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = signature.getMethod();
    
            // 获得切入目标对象
            Object target = proceedingJoinPoint.getThis();
            // 获得切入方法参数
            Object[] args = proceedingJoinPoint.getArgs();
            // 执行验证
            Set<ConstraintViolation<Object>> validResult = validateParameters(target, method, args);
    
            // 参数不合法返回
            if (validResult.size() > 0) {
                return processConstraintViolations(validResult);
            }
    
            return proceedingJoinPoint.proceed();
        }
    
        /**
         * 校验方法参数
         *
         * @param target 校验方法所在对象
         * @param method 将校验的方法
         * @param args   参数
         * @return 校验结果
         */
        public Set<ConstraintViolation<Object>> validateParameters(Object target, Method method, Object[] args) {
            // 校验结果
            Set<ConstraintViolation<Object>> result = new HashSet<>();
            // 先验证默认Validator校验范围
            Validator validator = validatorFactory.getValidator();
            ExecutableValidator executableValidator = validator.forExecutables();
            Set<ConstraintViolation<Object>> validResult = executableValidator.validateParameters(target, method, args);
            result.addAll(validResult);
    
            return result;
        }
    
        /**
         * 验证信息转MessageReturn
         *
         * @param violations 验证信息
         * @return 返回消息
         */
        public MessageReturn<Object> processConstraintViolations(Set<ConstraintViolation<Object>> violations) {
            // 仅取第一条错误
            StringBuilder sb = new StringBuilder();
            Iterator<ConstraintViolation<Object>> iterator = violations.iterator();
            if (iterator.hasNext()) {
                sb.append(iterator.next().getMessage()).append(";");
            }
    
            // 构建校验错误提示信息(使用已知消息提示)
            MessageReturn<Object> messageReturn = new MessageReturn<Object>();
            MessageReturn.setStatus(getCurrentAnnatation().errorCode());
            MessageReturn.setMessage(sb.toString());
    
            return MessageReturn;
        }
    
        /**
         * 获取 校验器工厂
         *
         * @return validatorFactory 校验器工厂
         */
        public ValidatorFactory getValidatorFactory() {
            return this.validatorFactory;
        }
    
        /**
         * 设置 校验器工厂
         *
         * @param validatorFactory 校验器工厂
         */
        public void setValidatorFactory(ValidatorFactory validatorFactory) {
            this.validatorFactory = validatorFactory;
        }
    
        /**
         * 获取 true:快速校验模式,false:全部校验模式
         *
         * @return validateModel true:快速校验模式,false:全部校验模式
         */
        public boolean isValidateModel() {
            return this.validateModel;
        }
    
        /**
         * 设置 true:快速校验模式,false:全部校验模式
         *
         * @param validateModel true:快速校验模式,false:全部校验模式
         */
        public void setValidateModel(boolean validateModel) {
            this.validateModel = validateModel;
        }
    
        /**
         * 获取 注解对象
         *
         * @return currentAnnatation 注解对象
         */
        public HValidate getCurrentAnnatation() {
            return this.currentAnnatation.get();
        }
    
        /**
         * 设置 注解对象
         *
         * @param hValidate 注解对象
         */
        public void setCurrentAnnatation(HValidate hValidate) {
            this.currentAnnatation.set(hValidate);
        }
    }
    

    使用示例

    接口定义

    实际工作中使用到Spring Cloud,就用这个做个示例了

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    MessageReturn<CustomObject> add(@RequestBody @HValidateGroup({CustomObjectGroup.Add.class }) CustomObject customObject);
    

    接口实现类

    @RestController
    // 这里作为切点进入
    @HValidate(errorCode = 10101000)
    // 下面这个是未知异常catch,请忽略
    @ErrorHandler(errorCode = 20101099)
    public class CustomServiceImpl implements CustomService {
    
        @Override
        // @HValidate(errorCode = 10101099)
        // 有需要的场景,这里的级别更高,出现不同的错误码
        public MessageReturn<CustomObject> add(@RequestBody CustomObject customObject) {
            // do something
            return xxxx;
        }
    

    校验规则

    /**
     * Demo
     *
     * @author wangzhuhua
     **/
    public class CustomObject {
    
        /** 标识 */
        @NotEmpty(message = "ID不可为空", groups = { CustomObjectGroup.Update.class })
        private String id;
    
        /** 名称 */
        @NotEmpty(message = "名称不能为空", groups = { CustomObjectGroup.Add.class,
                CustomObjectGroup.Update.class })
        private String name;
    }
    

    这样拓展了Hibernate validator,用起来方便,即可分组,也可自定义校验规则。

    相关文章

      网友评论

        本文标题:接口参数校验之JSR303+AOP

        本文链接:https://www.haomeiwen.com/subject/ejbzkqtx.html