zoukankan      html  css  js  c++  java
  • Springboot学习06-Spring AOP封装接口自定义校验

    Springboot学习06-Spring AOP封装接口自定义校验

    关键字

      BindingResult、Spring AOP、自定义注解、自定义异常处理、ConstraintValidator

    前言

       在实际项目中,对接口的传如的参数需要做校验处理,原来都是在接口里面直接进行if判断,虽然简单,但是每个接口都要重复写,显得冗余;并且返回的数据也无法很好的自定义说明校验情况;如下;

        @RequestMapping(value = { "/get/authcode" }, method = {RequestMethod.POST })
        public Object getSignInAuthCode(@RequestBody AuthCodeReq authCodeReq) throws Exception {
        //每个数据都要这样重复写    
        if(StringUtils.isBlank(authCodeReq.getMobile())){
          //返回数据也是固定格式,无法知道究竟是什么数据校验没通过     
         return ResponseMessageEnum.ARGUMENT_EXCEPTION.toString();
        }
         //业务逻辑略
        }

    正文

    0-封装全局数据校验功能的目的

      1-避免每个接口重复使用if判断,显得冗余

      2-可以自定义返回数据,告诉前端什么数据检验失败

    1-POST请求业务逻辑(且数据以json格式放在body中传入)

      1-将数据校验的具体内容放在POJO中,通过注解进行,见源码-01
      2-当前端URL请求(POST请求,且数据以json格式放在body中传入),且有数据校验失败时,传参BindException exception会接收校验失败的结果,见源码-02
      3-使用Spring AOP的前置方法处理数据检验,见源码-03
      3-1-自定义注解MyValidateAopAnnotation(见源码-04),用于定位AOP的Pointcut(见源码-03)
      3-2-AOP前置方法,根据joinPoint获取接口方法BindingResult参数值(见源码-03)
      3-3-如果bindingResult.hasErrors()为true,则表明数据校验没有通过(见源码-03),则直接抛出BindException异常()
      3-4-在GlobalExceptionHandler类中的BindExceptionHandler方法,专门处理BindException异常,返回json数据(见源码-05)

    2-GET请求

    3-源码分析

    //1-POJO
    import org.hibernate.validator.constraints.NotEmpty;
    import javax.validation.constraints.NotNull;
    public class ValidateAuthCodeReq {
        @NotEmpty(message = "手机号不能为空")//message将在接口返回数据中得以体现
        private String mobile;//手机号
        @NotEmpty(message = "验证码不能为空")
        private String code;//验证码
    
        public String getMobile() {
            return mobile;
        }
    
        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    }
    
    //2-controller层接口方法
    @RestController
    @RequestMapping(value="/api/shop/login")
    public class ApiShopLoginController extends ApiShopBaseController {
    
    
        //2-2-校验并绑定手机号
        @MyValidateAopAnnotation//自定义注解,用户AOP定位方法
        @RequestMapping(value = { "/validate/authcode" }, method = {RequestMethod.POST })
        public Object validateAndBind( @Valid  @RequestBody ValidateAuthCodeReq validateAuthCodeReq,BindingResult bindingResult) throws Exception {
                //BindingResult封装了数据校验结果
                //业务逻辑略
        }
    }
    
    //3-AOP方法,统一处理数据校验
    @Aspect
    @Component
    public class ExceptionAspect {
        //根据自定义注解MyValidateAopAnnotation定位方法
        @Pointcut("@annotation(com.hs.web.common.exception.MyValidateAopAnnotation)")
        public void bindPointCut() {
        }
    
        @Before("bindPointCut()")
        public void before(JoinPoint joinPoint) throws BindException {
            // 接收到请求
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            //获取请求的request
            HttpServletRequest request = attributes.getRequest();
            //获取BindingResult,并进行判断
            Object[] args = joinPoint.getArgs();
            BindingResult bindingResult = (BindingResult)args[args.length-1];
            if(bindingResult.hasErrors()){
                throw  new BindException(bindingResult);
            }
            System.out.println("args[args.length-1]: "+ args[args.length-1]);
            System.out.println("bindingResult" + bindingResult);
        }
    }
    
    //4-自定义注解MyValidateAopAnnotation-目的是为了AOP定位
    @Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    public @interface MyValidateAopAnnotation {
        
    }
    
    //5-全局异常处理类
    
    import com.hs.common.util.json.JsonUtil;
    @ControllerAdvice
    @ResponseBody
    public class GlobalExceptionHandler {
        //数据校验
        @ExceptionHandler(value=BindException.class)
        public String BindExceptionHandler(HttpServletRequest request,BindException exception) throws Exception{
            logger.warn(exception);
            //1-封装异常说明
            List<ObjectError> errorList = exception.getAllErrors();
            StringBuffer sb = new StringBuffer();
            for(ObjectError error : errorList){
                sb.append("参数" + exception.getFieldError().getField() + "异常:" + exception.getFieldError().getDefaultMessage()  + ";");
            }
            //2-封装返回参数
            ExceptionResponseBean detailBean = new ExceptionResponseBean(
                    GlobalExceptionEnum.ERROR_DATA_VALIDATION.getCode(),
                    GlobalExceptionEnum.ERROR_DATA_VALIDATION.getMsg(),
                    sb.toString());
            //3-以Json格式返回数据
            return JsonUtil.toJson(detailBean).toString();
        } 
    
    }
    
    //6-异常情况枚举
    
    import com.hs.common.util.json.JsonUtil;
    public enum GlobalExceptionEnum {
        
        OTHER_EXCEPTION(800, "出现异常", "其它异常,待识别", Exception.class),
        
        ERROR_DATA_VALIDATION(801, "数据校验异常", "请求参数数据校验异常", BindException.class),
    
        ;
        
        private int code;
        private String msg;
        private ExceptionResponseDetailBean data;
        private Class exception;
        private GlobalExceptionEnum(int code, String msg, String data, Class exception) {
            this.code = code;
            this.msg = msg;
            this.data = new ExceptionResponseDetailBean(data);
            this.exception = exception;
        }
    
        public int getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public ExceptionResponseDetailBean getData() {
            return data;
        }
    
        public Class getException() {
            return exception;
        }
    
    
    }
    
    
    //7-ExceptionResponseBean异常返回POJO
    public class ExceptionResponseBean {
        
        private int code;
        private String msg;
        private ExceptionResponseDetailBean data;
        
        public ExceptionResponseBean() {
            super();
        }
        
        public ExceptionResponseBean(int code, String msg, String detail) {
            super();
            this.code = code;
            this.msg = msg;
            this.data = new ExceptionResponseDetailBean(detail);
        }
    
        public int getCode() {
            return code;
        }
        public void setCode(int code) {
            this.code = code;
        }
        public String getMsg() {
            return msg;
        }
        public void setMsg(String msg) {
            this.msg = msg;
        }
        public ExceptionResponseDetailBean getData() {
            return data;
        }
        public void setData(ExceptionResponseDetailBean data) {
            this.data = data;
        }    
    
    }
    
    //8-ExceptionResponseDetailBean异常返回明细POJO
    
    public class ExceptionResponseDetailBean {
        
        private String detail;
    
        public ExceptionResponseDetailBean() {
            super();
        }
    
        public ExceptionResponseDetailBean(String detail) {
            super();
            this.detail = detail;
        }
    
        public String getDetail() {
            return detail;
        }
    
        public void setDetail(String detail) {
            this.detail = detail;
        }        
    }

    4-应用示例

     

    5-进一步优化校验

    5-1-优点和问题

    • 上面的数据校验已经进行分装,实现了和接口业务低耦合要求,并且可以自定义结果;但有一个问题:对于每一个POJO要校验的参数,都要重复指定message值,
    • 示例:@NotEmpty(message = "手机号不能为空") ;如果多个POJO对手机号验证,又会出现冗余情况

    5-2-优化思路

    • 自定义校验注解,对相同或类似的参数使用相同的自定义注解(见源码-01)
    • 自定义注解需要,自定义一个注解类(见源码-03)和一个ConstraintValidator实现类(见源码-03)

    5-3-源码分析

    //1-POJO
    package com.hs.api.shopapp.entity.commom;
    
    import com.hs.web.common.annotation.validation.MobileFormat;
    
    public class AuthCodeReq {
    
        @MobileFormat//使用自定义校验注解
        private String mobile;//手机号
    
        public String getMobile() {
            return mobile;
        }
    
        public void setMobile(String mobile) {
            this.mobile = mobile;
        }
    }
    
    //2-自定义注解MobileFormat
    @Documented
    @Target({ElementType.METHOD, ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = MobileConstraintValidator.class)
    public @interface MobileFormat {
        String message() default "手机号格式不正确";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface List {
            NotBlank[] value();
        }
    }
    
    
    //3-重写ConstraintValidator接口,自定义校验规则
    public class MobileConstraintValidator implements ConstraintValidator<MobileFormat,String>{
    
        @Override
        public void initialize(MobileFormat constraintAnnotation) {
        }
    
        //在当前方法指定校验规则
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if(StringUtil.isBlank(value)){
                return false;
            }
            return true;
        }
    }
    
    
    
    //4-测试接口
    @RestController
    @RequestMapping(value="/api/shop/login")
    public class ApiShopLoginController extends ApiShopBaseController {
    
        @MyValidateAopAnnotation
        @RequestMapping(value = { "/get/authcode" }, method = {RequestMethod.POST })
        public Object getSignInAuthCode(@Valid @RequestBody AuthCodeReq authCodeReq,BindingResult bindingResult) throws Exception {
            //业务逻辑略
        }
    
    
    }

    5-4-应用示例

    6-备注

    6-1-@NotEmpty、@NotNull、@NotBlank 的区别

    • @NotEmpty 用在集合上面(不能注释枚举)
    • @NotBlank用在String上面
    • @NotNull用在所有类型上面

    参考文献

    1-https://blog.csdn.net/ranshenyi/article/details/79548188
    2-https://www.cnblogs.com/NeverCtrl-C/p/8185576.html

  • 相关阅读:
    括号序列的dp问题模型
    粉刷匠
    木棍加工
    物流运输
    最短路图
    DP基础(线性DP)总结
    离散化
    树链剖分
    NOIP2016 “西湖边超萌小松鼠” 模拟赛
    NOI导刊 2009 提高二
  • 原文地址:https://www.cnblogs.com/wobuchifanqie/p/10181785.html
Copyright © 2011-2022 走看看