springboot优雅的做全局异常处理(完整代码+已运行使用)

首先我们需要知道的是,java异常的基础知识和springboot对异常处理是如何支持的。Java异常的基础知识大家可以参考菜鸟教程的java教程。

  1. springboot对异常处理的支持

在springboot中,我们只需要定义一个全局异常处理类(用@ControllerAdvice),不需要在每个controller类重复定义。在全局异常处理类中,我们使用@ExceptionHandler指定每个方法接收并处理哪种异常。经常我们还会使用@Slf4j和@ResponseBody做日志记录和返回json格式的数据。我定义的全局异常处理类如下:

 

  1. 优雅的处理全局异常

在上面定义的全局异常类中,我们可以看到有许多奇奇怪怪的类和方法,如BizException、ResultData等。

这些类是我自己定义的,作用是让全局异常更优雅,更易扩展,下面我将详细介绍如何优雅的处理全局异常。

2.1 总概

我写的全局异常处理包括了一个通用接口CommonException,一个自定义异常枚举类ExceptionEnum、一个公共异常封装类BizException、一个全局异常处理类GlobalExceptionHandler。

 

2.2 通用接口CommonException

package com.hy.exception;
/**
 * Description:
 * 作为一个公共的异常
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/21 16:55
 * @since JDK 1.8
 */
public interface CommonException {

    public int getExceptionCode();

    public String getExceptionMsg();

}

只定义了两个方法,目的是实现抽象为扩展提供可能(好吧,我知道你没听懂,不要着急,随着后面的阅读,我相信你会慢慢明白的)

2.3 自定义异常枚举类

package com.hy.exception;

/**
 * Description:
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/21 16:57
 * @since JDK 1.8
 */
public enum ExceptionEnum implements CommonException {

    /**
     * 所有的代码内部错误都抛出该异常
     */
    INTERNAL_ERROR(500, "内部服务异常"),

    /**
     * 如参数校验未通过,参数为空等抛出该异常
     */
    PARAMS_ERROR(100, "参数异常"),

    /**
     * 对爬虫等恶意访问抛出该异常
     */
    REQUEST_RATE_LIMIT(300, "请求超过速率"),

    /**
     * token校验未通过抛出该异常
     */
    TOKEN_ERROR(101, "token异常"),

    /**
     * token过期抛出该异常
     */
    TOKEN_EXPIRED(102, "token过期");

    private int exCode;
    private String exMsg;

    ExceptionEnum(int exCode, String exMsg) {
        this.exCode = exCode;
        this.exMsg = exMsg;
    }

    @Override
    public int getExceptionCode() {
        return exCode;
    }

    @Override
    public String getExceptionMsg() {
        return exMsg;
    }

}

可以看到,自定义的异常枚举类包含了exCode和exMsg两个属性,同时实现了CommonException接口,将getExceptionCode和getExceptionMsg两个方法分别实现,返回exCode和exMsg属性。

其中exCode和exMsg是我们根据自己需要完成的业务和可能出现的情况自定义的(值得一提的是,在经过一段时间完整项目Demo的开发后,发现对出现的偶然性错误我们常手工抛出异常,代替以往在单独代码中的输出错误或者提示重输)

这里我们开始了第一个优雅:让枚举类继承CommonException接口。

在这里继承CommonException接口后,原有的功能不多不少,但是却对ExceptionEnum做了抽象处理,在后面的BizException类中定义一个方法要传入ExceptionEnum对象作为参数时,我们对这个参数的类型定义不用ExceptionEnum而用CommonException,这样我们以后如果需要添加行的枚举类,就不用在ExceptionEnum中修改代码,只需要再新建一个类implements CommonException然后定义要添加的异常类型就可以了。

2.4 公共异常封装类BizException

package com.hy.exception;

/**
 * Description:
 * 公共异常封装类
 * 用于在逻辑代码中抛出异常时的封装
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/22 15:44
 * @since JDK 1.8
 */
public class BizException extends Exception implements CommonException{

    private int exCode;

    private String exMsg;

    @Override
    public int getExceptionCode() {
        return exCode;
    }

    @Override
    public String getExceptionMsg() {
        return exMsg;
    }

    /**
     * 使用自定义的异常枚举类构造异常
     * @param exceptionEnum
     */
    public BizException(CommonException exceptionEnum){

        this.exCode = exceptionEnum.getExceptionCode();
        this.exMsg = exceptionEnum.getExceptionMsg();
    }

    public BizException(int exCode, String exMsg){
        this.exCode = exCode;
        this.exMsg = exMsg;
    }

    public BizException(int exCode){
        this.exCode = exCode;
        this.exMsg = "未知错误";
    }

    public BizException(String exMsg){
        this.exCode = 10001;
        this.exMsg = exMsg;
    }
}

我们使用这个类来封装自定义的异常信息再抛出,这样我们在GlobalExceptionHandler类中就可以用一个单独的被@ExceptionHandler(value = BizException.class)注解的方法来处理自定义的异常。

这个类中继承Exception是为了将这个类变为异常类,实现CommonException只是为了复用,少写点代码。

同时,我们应该注意这个类的构造方法:

有一个接收CommonException类型的构造方法可以直接接收一个枚举类型,其他的则是另外零散的自定义异常信息的构造方法。

2.5 全局异常捕获处理类GlobalExceptionHandler

package com.hy.exception;

import com.hy.vo.ResultData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Description:
 * 全局异常捕获类
 * 所有的异常都会在这个类中处理
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/22 20:50
 * @since JDK 1.8
 */
@ControllerAdvice
@Slf4j
@ResponseBody
public class GlobalExceptionHandler {

    /**
     * 捕获自定义的几种异常
     *
     * @param ex
     * @return 异常码和异常信息封装的ResultData对象
     */
    @ExceptionHandler(value = BizException.class)
    public ResultData handlerBizException(Exception ex){

        // 向下转型使BizException的自定义的不同于父类Exception的方法可以使用
        BizException bizException = (BizException)ex;

        // 获得异常码和异常信息
        int exCode = bizException.getExceptionCode();
        String exMsg = bizException.getExceptionMsg();

        // 将异常码和异常信息封装成通用数据返回类型
        ResultData resultData = ResultData.fail(exCode, exMsg);

        // 对异常做日志记录,方便项目正式运行时发生异常后寻找异常发生点
        log.error(exCode + ":" +exMsg,bizException);

        // 向前端返回数据
        return resultData;
    }

    /**
     * 系统抛出的异常
     * @param ex
     * @return 固定异常码10002 和 异常信息封装的ResultData对象
     */
    @ExceptionHandler(value = Exception.class)
    public ResultData handlerException(Exception ex){

        // 对所以其他异常,统一异常码为10002,并封装
        ResultData resultData = ResultData.fail(10002,ex.getMessage());

        // 日志记录
        log.error(10002 + ":" +ex.getMessage(),ex);

        // 返回数据
        return resultData;
    }

}

一路走来,我们的主角终于要上场了。在全局异常捕获处理类中,我只定义了两个类,一个BizException的捕获处理和除BizException的其他所有异常类的捕获处理。可以看到,不管加多少自定义异常枚举类,我们都只要加自定义异常枚举类。

2.6 测试

 

发布了45 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Hitmi_/article/details/104085155
今日推荐