• Spring异常处理 ExceptionHandler的使用


    通常一个web程序在运行过程中,由于用户的操作不当,或者程序的bug,有大量需要处理的异常。其中有些异常是需要暴露给用户的,比如登陆超时,权限不足等等。可以通过弹出提示信息的方式告诉用户出了什么错误。

    而这就表示在程序中需要一个机制,去处理这些异常,将程序的异常转换为用户可读的异常。而且最重要的,是要将这个机制统一,提供统一的异常处理。因为我设计这个结构的主要目的是为了简化代码。

    在探寻spring的异常处理机制的时候,我分别使用了三种方式。三种方式都是使用的@ExceptionHandler注解。
    当一个Controller中有方法加了@ExceptionHandler之后,这个Controller其他方法中没有捕获的异常就会以参数的形式传入加了@ExceptionHandler注解的那个方法中。
    首先需要为自己的系统设计一个自定义的异常类,通过它来传递状态码。

    /** * Created by 47.
     * 自定义异常
     */
    public class SystemException extends RuntimeException{
        private String code;//状态码
        public SystemException(String message, String code) {
            super(message);
            this.code = code;
        }
        public String getCode() {
            return code;
        }
    }

    第一种思路,设计一个基类。

    /**
     * Created by 47.
     * 处理异常的类,需要处理异常的Controller直接继承这个类
     */
    public class BaseController {
        /**
         * 处理Controller抛出的异常
         * @param e 异常实例
         * @return Controller层的返回值
         */
        @ExceptionHandler
        @ResponseBody
        public Object expHandler(Exception e){
            if(e instanceof SystemException){
                SystemException ex= (SystemException) e;
                return WebResult.buildResult().status(ex.getCode())
                                .msg(ex.getMessage());
            }else{
                e.printStackTrace();
                return WebResult.buildResult().status(Config.FAIL)
                                .msg("系统错误");
            }
        }
    }

    之后所有需要异常处理的Controller都继承这个类,从而获取到异常处理的方法。
    虽然这种方式可以解决问题,但是极其不灵活,因为动用了继承机制就只为获取一个默认的方法,这显然是不好的。

    第二种思路,将这个基类变为接口,提供此方法的默认实现(也就是接口中的default方法,java8开始支持接口方法的默认实现)

    /**
     * Created by 47.
     * 接口形式的异常处理
     */
    public interface DataExceptionSolver {
        @ExceptionHandler
        @ResponseBody
        default Object exceptionHandler(Exception e){
            try {
                throw e;
            } catch (SystemException systemException) {
                systemException.printStackTrace();
                return WebResult.buildResult().status(systemException.getCode())
                        .msg(systemException.getMessage());
            } catch (Exception e1){
                e1.printStackTrace();
                return WebResult.buildResult().status(Config.FAIL)
                        .msg("系统错误");
            }
        }
    }

    这种方式虽然没有占用继承,但是也不是很优雅,因为几乎所有的Controller都需要进行异常处理,于是我每个Controller都需要去写implement DataExceptionSolver,这显然不是我真正想要的。况且这种方式依赖java8才有的语法,这是一个很大的局限。

    第三种思路,使用加强Controller做全局异常处理。
    所谓加强Controller就是@ControllerAdvice注解,有这个注解的类中的方法的某些注解会应用到所有的Controller里,其中就包括@ExceptionHandler注解。
    于是可以写一个全局的异常处理类:

    /**
     * Created by 47
     * 全局异常处理,捕获所有Controller中抛出的异常。
     */
    @ControllerAdvice
    public class GlobalExceptionHandler {
       //处理自定义的异常
       @ExceptionHandler(SystemException.class) 
       @ResponseBody
       public Object customHandler(SystemException e){
          e.printStackTrace();
          return WebResult.buildResult().status(e.getCode()).msg(e.getMessage());
       }
       //其他未处理的异常
       @ExceptionHandler(Exception.class)
       @ResponseBody
       public Object exceptionHandler(Exception e){
          e.printStackTrace();
          return WebResult.buildResult().status(Config.FAIL).msg("系统错误");
       }
    }

    或者:

    @ControllerAdvice
    public class GlobalExceptionHandler {
    
        private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    
        @ExceptionHandler(Exception.class)
        @ResponseBody
        public Result dbExceptionHandler(Exception e){
            logger.error("GlobalExceptionHandler: {}", e);
            return ResultBuilder.failure(null).msg(e.getMessage()).build();
        }
    }

    这个类中只处理了两个异常,但是已经满足了大部分需要,如果还有需要特殊处理的地方,可以再加上处理的方法就行了。第三种实现方式是目前我知道的最优雅的方式了。
    如此,我们现在的Controller中的方法就可以很简洁了,比如处理登陆的逻辑就可以这样简单的写:

    /**
     * Created by 47
     * 账号
     */
    @RestController
    @RequestMapping("passport")
    public class PassportController {
        PassportService passportService;
            @RequestMapping("login")
        public Object doLogin(HttpSession session, String username, String password){
            User user = passportService.doLogin(username, password);
            session.setAttribute("user", user);
            return WebResult.buildResult().redirectUrl("/student/index");
        }
    }

    而在passprotService的doLogin方法中,可能会抛出用户名或密码错误等异常,然后就会交由GlobalExceptionHandler去处理,直接返回异常信息给前端,然后前端也不需要关心是否返回了异常,因为这些都已经定义好了。
    前端js代码只需要这样写:

    //登陆
    AJAX.POST("/passport/login", {
        username:name,
        password:psw
    })

    一个异常在其中流转的过程为:
    比如doLogin方法抛出了自定义异常,其code为:FAIL,message为:用户名或密码错误,由于在controller的方法中没有捕获这个异常,所以会将异常抛给GlobalExceptionHandler,然后GlobalExceptionHandler通过WebResult将状态码和提示信息返回给前端,前端通过默认的处理函数,弹框提示用户“用户名或密码错误”。而对于这样的一次交互,我们根本不用编写异常处理部分的逻辑。

    到这里,代码已经简洁了很多,而且重用性大大提高。

  • 相关阅读:
    函数式编程与命令式编程的学习难度比较
    Swfit4.0中JSON与模型原生互转(JSONEncoder/JSONDecoder的使用)
    元类型与类型的区别
    Swift
    swift类型操作规范
    PHP实现执行定时任务的几种思路详解
    基于ThinkPHP与阿里大于的PHP短信验证功能
    laravel中将session由文件保存改为数据库保存
    laravel5.*安装使用Redis以及解决Class 'PredisClient' not found和Fatal error: Non-static method Redis::set() cannot be called statically错误
    Python基础知识汇总
  • 原文地址:https://www.cnblogs.com/47Gamer/p/13851073.html
Copyright © 2020-2023  润新知