zoukankan      html  css  js  c++  java
  • SpringBoot全局异常捕获处理及参数校验

    一,为什么要用全局异常处理?

    在日常开发中,为了不抛出异常堆栈信息给前端页面,每次编写Controller层代码都要尽可能的catch住所有service层、dao层等异常,代码耦合性较高,且不美观,不利于后期维护。

    为解决该问题,计划将Controller层异常信息统一封装处理,且能区分对待Controller层方法返回给前端的String、Map、JSONObject、ModelAndView等结果类型。

    二,应用场景是什么?
    • 非常方便的去掉了try catch这类冗杂难看的代码,有利于代码的整洁和优雅
    • 自定义参数校验时候全局异常处理会捕获异常,将该异常统一返回给前端,省略很多if else代码
    • 当后端出现异常时,需要返回给前端一个友好的界面的时候就需要全局异常处理
    • 因为异常时层层向上抛出的,为了避免控制台打印一长串异常信息
    三、如何进行全局异常捕获和处理?

    一共有两种方法:

    • Spring的AOP(较复杂
    • @ControllerAdvice结合@ExceptionHandler(简单)
    四、@ControllerAdvice和@ExceptionHandler怎么用?

    1、Controller Advice字面上意思是“控制器通知”,Advice除了“劝告”、“意见”之外,还有“通知”的意思。

    可以将@ExceptionHandler(标识异常类型对应的处理方法)标记的方法提取出来,放到一个类里,并将加上@ControllerAdvice,这样,所有的控制器都可以用了

    @ControllerAdvice
    public class ControllerHandlers(){
    @ExceptionHandler
        public String errorHandler(Exception e){
            return "error";
        }
    }

    2、 因为@ControllerAdvice@Componen标记,所以他可以被组件扫描到并放入Spring容器

    3、 如果只想对一部分控制器通知,比如某个包下边的控制器,就可以这样写:

    @ControllerAdvice("com.labor")
    public class ControllerHandlers(){}

    也可以直接写类名

    @ControllerAdvice(basePackageClasses = ***.class)
    public class ControllerHandlers(){}

    也可以传多个类

    @ControllerAdvice(assignableTypes = {***.class,***.class})
    public class ControllerHandlers(){}

    4、 控制器通知还有一个兄弟,@RestControllerAdvice如果用了它,错误处理方法的返回值不会表示用的哪个视图,而是会作为HTTP body处理,

           即相当于错误处理方法加@ResponseBody注解。

    @RestControllerAdvice
    public class ControllerHandlers(){
    @ExceptionHandler
        public String errorHandler(Exception e){
            return "error";
        }
    }

    5、@ExceptionHandler注解的方法只能返回一种类型,在前后端分离开发中我们通常返回,统一返回类型和优化错误的提示,我们可以封装我们自己的返回Map
    public class AjaxResult extends HashMap<String, Object> {

        private static final long serialVersionUID = 1L;

        public static final String CODE_TAG = "code";

        public static final String MSG_TAG = "msg";

        public static final String DATA_TAG = "data";

        /**
         * 状态类型
         */
        public enum Type {
            /**
             * 成功
             */
            SUCCESS(1),
            /**
             * 警告
             */
            WARN(2),
            /**
             * 错误
             */
            ERROR(0),
            /**无权限*/
            UNAUTH(3),
            /**未登录、登录超时*/
            UNLOGIN(4);
            private final int value;

            Type(int value) {
                this.value = value;
            }

            public int value() {
                return this.value;
            }
        }

        /**
         * 状态类型
         */
        private Type type;

        /**
         * 状态码
         */
        private int code;

        /**
         * 返回内容
         */
        private String msg;

        /**
         * 数据对象
         */
        private Object data;

        /**
         * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
         */
        public AjaxResult() {
        }

        /**
         * 初始化一个新创建的 AjaxResult 对象
         * @param type 状态类型
         * @param msg  返回内容
         */
        public AjaxResult(Type type, String msg) {
            super.put(CODE_TAG, type.value);
            super.put(MSG_TAG, msg);
        }

        /**
         * 初始化一个新创建的 AjaxResult 对象
         * @param type 状态类型
         * @param msg  返回内容
         * @param data 数据对象
         */
        public AjaxResult(Type type, String msg, Object data) {
            super.put(CODE_TAG, type.value);
            super.put(MSG_TAG, msg);
            /* 数据为空的时候,还是需要把参数传给前台   huangqr @2019.7.19
            if (StringUtils.isNotNull(data)) {
                super.put(DATA_TAG, data);
            }*/
            super.put(DATA_TAG, data);
        }

        /**
         * 返回成功消息
         * @return 成功消息
         */
        public static AjaxResult success() {
            return AjaxResult.success("操作成功");
        }

        /**
         * 返回成功数据
         * @return 成功消息
         */
        public static AjaxResult success(Object data) {
            return AjaxResult.success("操作成功", data);
        }

        /**
         * 返回成功消息
         * @param msg 返回内容
         * @return 成功消息
         */
        public static AjaxResult success(String msg) {
            return AjaxResult.success(msg, null);
        }

        /**
         * 返回成功消息
         * @param msg  返回内容
         * @param data 数据对象
         * @return 成功消息
         */
        public static AjaxResult success(String msg, Object data) {
            return new AjaxResult(Type.SUCCESS, msg, data);
        }

        /**
         * 返回警告消息
         * @param msg 返回内容
         * @return 警告消息
         */
        public static AjaxResult warn(String msg) {
            return AjaxResult.warn(msg, null);
        }

        /**
         * 返回警告消息
         * @param msg  返回内容
         * @param data 数据对象
         * @return 警告消息
         */
        public static AjaxResult warn(String msg, Object data) {
            return new AjaxResult(Type.WARN, msg, data);
        }

        /**
         * 返回错误消息
         * @return
         */
        public static AjaxResult error() {
            return AjaxResult.error("操作失败");
        }

        /**
         * 返回错误消息
         * @param msg 返回内容
         * @return 警告消息
         */
        public static AjaxResult error(String msg) {
            return AjaxResult.error(msg, null);
        }

        /**
         * 返回错误消息
         * @param msg  返回内容
         * @param data 数据对象
         * @return 警告消息
         */
        public static AjaxResult error(String msg, Object data) {
            return new AjaxResult(Type.ERROR, msg, data);
        }

        /**
         * 无权限返回
         * @return
         */
        public static AjaxResult unauth() {
            return new AjaxResult(Type.UNAUTH, "您没有访问权限!", null);
        }
        /**
         * 无权限
         *
         * @param msg
         * @return com.wanda.labor.framework.web.domain.AjaxResult
         * @exception
         */
        public static AjaxResult unauth(String msg) {
            return new AjaxResult(Type.UNAUTH, msg, null);
        }
        /**
         * 未登录或登录超时。请重新登录
         *
         * @param
         * @return com.wanda.labor.framework.web.domain.AjaxResult
         * @exception
         */
        public static AjaxResult unlogin() {
            return new AjaxResult(Type.UNLOGIN, "未登录或登录超时。请重新登录!", null);
        }

        public Type getType() {
            return type;
        }

        public void setType(Type type) {
            this.type = type;
        }

        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 Object getData() {
            return data;
        }

        public void setData(Object data) {
            this.data = data;
        }

        public static class SUCCESS{

            public static AjaxResult data(Object data){
                return new AjaxResult(Type.SUCCESS, "操作成功 Operation Successful", data);
            }

            public static AjaxResult iMessagesg(String msg){
                return new AjaxResult(Type.SUCCESS, msg, null);
            }

            public static AjaxResult imsgAndData(String msg,Object data){
                return new AjaxResult(Type.SUCCESS, msg, data);
            }
        }

        @Override
        public String toString() {
            return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("code", getCode())
                    .append("msg", getMsg()).append("data", getData()).toString();
        }
    }

    6、 完善全局异常处理器
    @RestControllerAdvice
    public class GlobalExceptionHandler {
        private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

        /**
         * 权限校验失败 如果请求为ajax返回json,普通请求跳转页面
         */
        @ExceptionHandler(AuthorizationException.class)
        public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e) {
            //log.error(e.getMessage(), e);
            if (ServletUtils.isAjaxRequest(request)) {
                return AjaxResult.unauth(PermissionUtils.getMsg(e.getMessage()));
            } else {
                ModelAndView modelAndView = new ModelAndView();
                modelAndView.setViewName("error/unauth");
                return modelAndView;
            }
        }

        /**
         * 请求方式不支持
         */
        @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
        public AjaxResult handleException(HttpRequestMethodNotSupportedException e) {
            log.error(e.getMessage(), e);
            return AjaxResult.error("不支持' " + e.getMethod() + "'请求");
        }

        /**
         * 拦截未知的运行时异常
         */
        @ExceptionHandler(RuntimeException.class)
        public AjaxResult notFount(RuntimeException e) {
            log.error("运行时异常:", e);
            return AjaxResult.error("运行时异常:" + e.getMessage());
        }

        /**
         * 系统异常
         */
        @ExceptionHandler(Exception.class)
        public AjaxResult handleException(Exception e) {
            log.error(e.getMessage(), e);
            return AjaxResult.error("服务器错误,请联系管理员");
        }

        /**
         * 校验异常
         */
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public AjaxResult exceptionHandler(MethodArgumentNotValidException e) {
            BindingResult bindingResult = e.getBindingResult();
            String errorMesssage = "";
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                errorMesssage += fieldError.getDefaultMessage() + "!";
            }
            return AjaxResult.error(errorMesssage);
        }

        /**
         * 校验异常
         */
        @ExceptionHandler(value = BindException.class)
        public AjaxResult validationExceptionHandler(BindException e) {
            BindingResult bindingResult = e.getBindingResult();
            String errorMesssage = "";
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                errorMesssage += fieldError.getDefaultMessage() + "!";
            }
            return AjaxResult.error(errorMesssage);
        }

        /**
         * 校验异常
         */
        @ExceptionHandler(value = ConstraintViolationException.class)
        public AjaxResult ConstraintViolationExceptionHandler(ConstraintViolationException ex) {
            Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
            Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
            List<String> msgList = new ArrayList<>();
            while (iterator.hasNext()) {
                ConstraintViolation<?> cvl = iterator.next();
                msgList.add(cvl.getMessageTemplate());
            }
            return AjaxResult.error(String.join(",",msgList));
        }

        /**
         * 业务异常
         */
        @ExceptionHandler(BusinessException.class)
        public AjaxResult businessException(BusinessException e) {
            log.error(e.getMessage(), e);
            return AjaxResult.error(e.getMessage());
        }

        /**
         * 演示模式异常
         */
        @ExceptionHandler(DemoModeException.class)
        public AjaxResult demoModeException(DemoModeException e) {
            return AjaxResult.error("演示模式,不允许操作");
        }
    }

    六、@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了

    pom文件引入validation的jar包。推荐:250期面试题

    <!-- 校验-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

    等待校验的object

    public class Person {
        /**
         * @PersonName(prefix = "song"):自定义注解
         */
        @NotNull
        @PersonName(prefix = "song")
        private String name;
        @Min(value = 18)
        @Max(value = 30, message = "超过30岁的不要!")
        private Integer age;
    }

    自定义注解

    • https://blog.csdn.net/panchang199266/article/details/83050053

    使用

    /**
    * 开启校验注解:@Valid
    */
    @RestController
    public class PersonController {
        @PostMapping("/person")
        public Person savePerson(@Valid @RequestBody Person person){
            return person;
        }
    }

    全局异常处理里有相应的处理方法

    /**
    * 校验异常
    */
    @ExceptionHandler(value = BindException.class)
    public AjaxResult validationExceptionHandler(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        String errorMesssage = "";
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            errorMesssage += fieldError.getDefaultMessage() + "!";
        }
        return AjaxResult.error(errorMesssage);
    }

    @RequestBody@RequestParam注解的请求实体,校验异常类是不同的

    七、自定义异常以及事务回滚
    public class MyException extends RuntimeException {
        //这个地方不要写exception,因为Spring是只对运行时异常进行事务回滚,
        //如果抛出的是exception是不会进行事务回滚的。
    }

    如果是在service层里捕获异常统一去处理,那为了保证事务的回滚,需要抛出RuntimeException

    try { 

    ……// to do something for database 
    } catch (Exception e) {
      e.printStackTrace();
      logger.error("发生异常");
      throw new RuntimeException();
    }

    关于try-catch-finally中,finally的作用,finally设计之初就是为了关闭资源,如果在finally中使用return语句,会覆盖try或者catch的返回值,

    最常见的就是覆盖异常,即便catch往上抛了异常,也会被覆盖,返回finally中return语句的返回值。

    努力做一个伪程序员, 不管道路有多么的艰苦......
  • 相关阅读:
    JQuery实现1024小游戏
    Windows Server2008 R2安装wampserver缺少api-ms-win-crt-runtime-l1-1-0.dll解决方案
    ASP.NET MVC 邮件发送的功能(微软邮箱发送)。
    浅谈撞库防御策略
    极验高并发验证服务背后的技术实现
    2015年国内数据安全事件盘点
    转载——验证码的昨天、今天和明天
    转载——最近百度云盘不提供搜索,闲来无事,玩玩python爬虫,爬一下百度云盘的资源
    SQL 查询语句
    SQL Server 目录
  • 原文地址:https://www.cnblogs.com/xumBlog/p/15140923.html
Copyright © 2011-2022 走看看