SpringBoot系列——自定义统一异常处理

  前言

  springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理。

  

  1、使用@ControllerAdvice、@RestControllerAdvice捕获运行时异常。

  2、重写ErrorController,手动抛出自定义ErrorPageException异常,方便404、403等被统一处理。

 

  官网文档相关介绍:

  https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-error-handling

  

 

  代码

  项目结构

 

 

  引入我们父类pom即可,无需引入其他依赖

 

  开始之前,需要先定下统一返回对象、自定义异常枚举类

/**
 * 自定义异常枚举类
 */
public enum ErrorEnum {
    //自定义系列
    USER_NAME_IS_NOT_NULL("10001","【参数校验】用户名不能为空"),
    PWD_IS_NOT_NULL("10002","【参数校验】密码不能为空"),

    //400系列
    BAD_REQUEST("400","请求的数据格式不符!"),
    UNAUTHORIZED("401","登录凭证过期!"),
    FORBIDDEN("403","抱歉,你无权限访问!"),
    NOT_FOUND("404", "请求的资源找不到!"),

    //500系列
    INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
    SERVICE_UNAVAILABLE("503","服务器正忙,请稍后再试!"),

    //未知异常
    UNKNOWN("10000","未知异常!");


    /** 错误码 */
    private String code;

    /** 错误描述 */
    private String msg;

    ErrorEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
/**
 * 统一返回对象
 */

@Data
public class Result<T> implements Serializable {
    /**
     * 通信数据
     */
    private T data;
    /**
     * 通信状态
     */
    private boolean flag = true;
    /**
     * 通信描述
     */
    private String msg = "操作成功";

    /**
     * 通过静态方法获取实例
     */
    public static <T> Result<T> of(T data) {
        return new Result<>(data);
    }

    public static <T> Result<T> of(T data, boolean flag) {
        return new Result<>(data, flag);
    }

    public static <T> Result<T> of(T data, boolean flag, String msg) {
        return new Result<>(data, flag, msg);
    }

    public static <T> Result<T> error(ErrorEnum errorEnum) {
        return new Result(errorEnum.getCode(), false, errorEnum.getMsg());
    }

    @Deprecated
    public Result() {

    }

    private Result(T data) {
        this.data = data;
    }

    private Result(T data, boolean flag) {
        this.data = data;
        this.flag = flag;
    }

    private Result(T data, boolean flag, String msg) {
        this.data = data;
        this.flag = flag;
        this.msg = msg;
    }

}

 

  新增两个自定义异常,便于统一处理时捕获异常

/**
 * 自定义业务异常
 */
public class ServiceException extends RuntimeException {

    /**
     * 自定义异常枚举类
     */
    private ErrorEnum errorEnum;

    /**
     * 错误码
     */
    private String code;

    /**
     * 错误信息
     */
    private String errorMsg;


    public ServiceException() {
        super();
    }

    public ServiceException(ErrorEnum errorEnum) {
        super("{code:" + errorEnum.getCode() + ",errorMsg:" + errorEnum.getMsg() + "}");
        this.errorEnum = errorEnum;
        this.code = errorEnum.getCode();
        this.errorMsg = errorEnum.getMsg();
    }

    public ServiceException(String code,String errorMsg) {
        super("{code:" + code + ",errorMsg:" + errorMsg + "}");
        this.code = code;
        this.errorMsg = errorMsg;
    }

    public ErrorEnum getErrorEnum() {
        return errorEnum;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

 

/**
 * 自定义错误页面异常
 */
public class ErrorPageException extends ServiceException {

    public ErrorPageException(String code,String msg) {
        super(code, msg);
    }
}

 

  重写ErrorController,不在跳转原生错误页面,而是抛出我们的自定义异常

/**
 * 自定义errorPage
 * 直接继承 BasicErrorController
 */
@Controller
public class ErrorPageConfig extends BasicErrorController {

    public ErrorPageConfig(){
        super(new DefaultErrorAttributes(),new ErrorProperties());
    }

    @Override
    @RequestMapping(
            produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        doError(request);
        return null;
    }

    @Override
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        doError(request);
        return null;
    }

    private void doError(HttpServletRequest request) {
        Map<String, Object> model = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));

        //抛出ErrorPageException异常,方便被ExceptionHandlerConfig处理
        String path = model.get("path").toString();
        String status = model.get("status").toString();

        //静态资源文件发生404,无需抛出异常
        if(!path.contains("/common/") && !path.contains(".")){
            throw new ErrorPageException(status, path);
        }
    }
}

 

   @RestControllerAdvice,统一异常处理,捕获并返回统一返回对象Result,同时把异常信息打印到日志中

/**
 * 统一异常处理
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerConfig{

    /**
     * 业务异常 统一处理
     */
    @ExceptionHandler(value = ServiceException.class)
    @ResponseBody
    public Object exceptionHandler400(ServiceException e){
        return returnResult(e,Result.error(e.getErrorEnum()));
    }

    /**
     * 错误页面异常 统一处理
     */
    @ExceptionHandler(value = ErrorPageException.class)
    @ResponseBody
    public Object exceptionHandler(ErrorPageException e){
        ErrorEnum errorEnum;
        switch (Integer.parseInt(e.getCode())) {
            case 404:
                errorEnum = ErrorEnum.NOT_FOUND;
                break;
            case 403:
                errorEnum = ErrorEnum.FORBIDDEN;
                break;
            case 401:
                errorEnum = ErrorEnum.UNAUTHORIZED;
                break;
            case 400:
                errorEnum = ErrorEnum.BAD_REQUEST;
                break;
            default:
                errorEnum = ErrorEnum.UNKNOWN;
                break;
        }

        return returnResult(e,Result.error(errorEnum));
    }

    /**
     * 空指针异常 统一处理
     */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public Object exceptionHandler500(NullPointerException e){
        return returnResult(e,Result.error(ErrorEnum.INTERNAL_SERVER_ERROR));
    }

    /**
     * 其他异常 统一处理
     */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public Object exceptionHandler(Exception e){
        return returnResult(e,Result.of(ErrorEnum.UNKNOWN.getCode(), false, "【" + e.getClass().getName() + "】" + e.getMessage()));
    }

    /**
     * 是否为ajax请求
     * ajax请求,响应json格式数据,否则应该响应html页面
     */
    private Object returnResult(Exception e,Result errorResult){
        //把错误信息输入到日志中
        log.error(ErrorUtil.errorInfoToString(e));

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();

        //设置http响应状态
        response.setStatus(200);

        //判断是否为ajax请求
        if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))){
            return errorResult;
        }else{
            return new ModelAndView("error","msg",errorResult.getMsg());
        }
    }
}

 

  新建测试controller,新增几个测试接口,模拟多种异常报错的情况

/**
 * 模拟异常测试
 */
@RestController
@RequestMapping("/test/")
public class TestController {
    /**
     * 正常返回数据
     */
    @GetMapping("index")
    public Result index(){
        return Result.of("正常返回数据");
    }

    /**
     * 模拟空指针异常
     */
    @GetMapping("nullPointerException")
    public Result nullPointerException(){
        //故意制造空指针异常
        String msg = null;
        msg.equals("huanzi-qch");
        return Result.of("正常返回数据");
    }

    /**
     * 模拟业务异常,手动抛出业务异常
     */
    @GetMapping("serviceException")
    public Result serviceException(){
        throw new ServiceException(ErrorEnum.USER_NAME_IS_NOT_NULL);
    }
}

 

  效果

  正常数据返回

  http://localhost:10010/test/index

 

 

  模拟空指针异常

  http://localhost:10010/test/nullPointerException

 

 

  模拟业务异常

  http://localhost:10010/test/serviceException

 

   调用错误接口,404

  http://localhost:10010/test/serviceException111

 

  更新

  2022-03-22更新:统一异常处理返回格式调整:ajax请求返回json格式数据,其他情况下跳转自定义error页面

 

 

 

  后记

  自定义统一异常处理暂时先记录到这,后续再进行补充。

  

  代码开源

 

  代码已经开源、托管到我的GitHub、码云:

  GitHub:https://github.com/huanzi-qch/springBoot

  码云:https://gitee.com/huanzi-qch/springBoot

posted @ 2021-05-20 11:15  huanzi-qch  阅读(6345)  评论(0编辑  收藏  举报