Springboot自定义异常以及统一异常处理RestControllerAdvice(ControllerAdvice)

一、背景

       开发中可能碰到各种各样的异常,有的是可自定义的业务异常,有的是运行中不可知的异常,这就需要有个统一的处理。另外如果给前端返回乱七八糟的错误信息,前端就很难告知用户有效的信息,这就需要跟前端约定一个统一的规范,照着这个规范给出响应。

二、自定义异常错误码

首先,得自定义一些异常,这个根据自己需要添加:

/**
 * Description 异常错误码
 *
 * @author Bob
 * @date 2020/8/17
 **/
public final class ErrorCode {

    /**
     * 成功
     */
    public static final String SUCCESS = "000000";

    /**
     * 参数验证异常
     */
    public static final String INVALID_ARG_EXCEPTION = "000001";

    /**
     * 业务异常
     */
    public static final String BIZ_EXCEPTION = "000002";

    /**
     * 数据库异常
     */
    public static final String DAO_EXCEPTION = "000003";

    /**
     * 内部服务器错误
     */
    public static final String SERVLET_EXCEPTION = "000004";

    /**
     * DATAFLOW异常
     */
    public static final String DATAFLOW_EXCEPTION = "000005";

    /**
     * 空指针异常
     */
    public static final String NULL_POINTER_EXCEPTION = "000006";
    /**
     * 系统异常
     */
    public static final String EXCEPTION = "999999";
}

三、自定义异常

传统处理异常一般有以下烦恼:

  • 是捕获异常(try…catch)还是抛出异常(throws)
  • 是在controller层做处理还是在service层处理又或是在dao层做处理
  • 处理异常的方式是啥也不做,还是返回特定数据,如果返回又返回什么数据
  • 不是所有异常我们都能预先进行捕捉,如果发生了没有捕捉到的异常该怎么办?

以上这些问题都可以用全局异常处理来解决,全局异常处理也叫统一异常处理,全局和统一处理代表什么?代表规范!规范有了,很多问题就会迎刃而解!

抛异常的时候总不能每次都抛Exception,这里需要配置一些自定义的异常:

  • 自定义异常可以携带更多的信息,不像这样只能携带一个字符串。
  • 项目开发中经常是很多人负责不同的模块,使用自定义异常可以统一了对外异常展示的方式。
  • 自定义异常语义更加清晰明了,一看就知道是项目中手动抛出的异常。
/**
 * Description 默认异常
 *
 * @author Bob
 * @date 2020/8/17
 **/
public class DataFlowException extends RuntimeException {
    private static final long serialVersionUID = 5688133194852743474L;
    /**
     * @description 错误码
     * @author Bob
     * @date 2020/8/17
     */
    private String errCode = ErrorCode.DATAFLOW_EXCEPTION;

    public String getErrCode() {
        return errCode;
    }

    public void setErrCode(String errCode) {
        this.errCode = errCode;
    }

    /**
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public DataFlowException(String message) {
        super(message);
    }

    /**
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public DataFlowException(String errCode, String errMsg) {
        super(errMsg);
        this.setErrCode(errCode);
    }

    /**
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public DataFlowException(String message, Throwable cause) {
        super(message, cause);
    }

    /**
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public DataFlowException(String errCode, String message, Throwable cause) {
        super(message, cause);
        this.setErrCode(errCode);
    }

    /**
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public DataFlowException(Throwable cause) {
        super(cause);
    }
}

 

/**
 * Description 业务异常
 *
 * @author Bob
 * @date 2020/8/17
 **/
public class BusinessException extends DataFlowException {
    private static final long serialVersionUID = -8609214742345497948L;

    /**
     * @param message
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public BusinessException(String message) {
        super(message);
        setErrCode(ErrorCode.BIZ_EXCEPTION);
    }

    /**
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public BusinessException(String errCode, String errMsg) {
        super(errMsg);
        this.setErrCode(errCode);
    }

    /**
     * @param message
     * @param cause
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
        setErrCode(ErrorCode.BIZ_EXCEPTION);
    }

    /**
     * @param errCode
     * @param message
     * @param cause
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public BusinessException(String errCode, String message, Throwable cause) {
        super(message, cause);
        setErrCode(errCode);
    }

    /**
     * @param cause
     * @description 构造方法
     * @author Bob
     * @date 2020/8/17
     */
    public BusinessException(Throwable cause) {
        super(cause);
        setErrCode(ErrorCode.BIZ_EXCEPTION);
    }
}

四、统一响应

与前端约定,返回统一的格式:code:**,msg:**,data:**

import java.io.Serializable;

public class RestResponse<T> implements Serializable {
    private static final long serialVersionUID = 4893280118017319089L;
    public static final String SUCCESS_CODE = "0";
    public static final String SUCCESS_MESSAGE = "操作成功";
    public static final String ERROR_CODE = "9";
    public static final String ERROR_MESSAGE = "系统异常";
    private String code;
    private String msg;
    private T data;

    public RestResponse() {
        this("0", "操作成功");
    }

    public RestResponse(String code, String msg) {
        this(code, msg, (Object)null);
    }

    public RestResponse(String code, String msg, T data) {
        this.code(code).msg(msg).result(data);
    }

    private RestResponse<T> code(String code) {
        this.setCode(code);
        return this;
    }

    private RestResponse<T> msg(String msg) {
        this.setMsg(msg);
        return this;
    }

    public RestResponse<T> result(T data) {
        this.setData(data);
        return this;
    }

    public boolean success() {
        return "0".equals(this.code);
    }

    public boolean fail() {
        return !this.success();
    }

    public static <T> RestResponse<T> ok(T t) {
        return new RestResponse("0", "操作成功", t);
    }

    public static <T> RestResponse<T> ok() {
        return new RestResponse();
    }

    public static <E> RestResponse<E> error(String code, String msg) {
        return new RestResponse(code, msg);
    }

    public static <T> RestResponse<T> error() {
        return new RestResponse("9", "系统异常");
    }

    public static <T> RestResponse<T> error(String msg) {
        return new RestResponse("9", msg);
    }

    public String getCode() {
        return this.code;
    }

    public String getMsg() {
        return this.msg;
    }

    public T getData() {
        return this.data;
    }

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

    public void setMsg(String msg) {
        this.msg = msg;
    }

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

    public String toString() {
        return "RestResponse(code=" + this.getCode() + ", msg=" + this.getMsg() + ", data=" + this.getData() + ")";
    }
}

如果都按统一格式返回的话,就不用讨论下一步了。关键是代码不可能一个人开发,每个人都有自己的习惯,当然肯定也有不可预知的异常,比如:

校验请求参数是否为空,可以这么写:

Preconditions.checkNotNull(req.getPort(), "port不能为空");

也可以在请求体中添加@NotNull 推荐这个,代码简洁

@NotNull(message = "port不能为空")
private String port;

可是,这么写,返回的时候就变成这样了:

{
    "timestamp": "2020-09-27 14:03:22",
    "status": 500,
    "error": "Internal Server Error",
    "message": "port不能为空",
    "path": "/datasource/queryTableList"

这明显不是我们要的格式啊,我们要的是下面这样的:

{
    "code": "000006",
    "msg": "port不能为空",
    "data": null

 怎么办呢,Spring早就内置了这种处理方案,就是RestControllerAdvice(ControllerAdvice),结合ExceptionHandler

五、全局异常捕获统一处理 RestControllerAdvice(ControllerAdvice)

首先,我们需要新建一个类,在这个类上加上@ControllerAdvice或@RestControllerAdvice注解,这个类就配置成全局处理类了。(这个根据你的Controller层用的是@Controller还是@RestController来决定)

然后在类中新建方法,在方法上加上@ExceptionHandler注解并指定你想处理的异常类型,接着在方法内编写对该异常的操作逻辑,就完成了对该异常的全局处理!

先来看下RestControllerAdvice的源码:

package org.springframework.web.bind.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}

 再看下ControllerAdvice的源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};

    @AliasFor("value")
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};

    Class<?>[] assignableTypes() default {};

    Class<? extends Annotation>[] annotations() default {};
}

 发现RestControllerAdvice 其实就是ControllerAdvice和ResponseBody的结合体

 算了,还是直接上自定义的代码吧

import com.***.common.exception.BusinessException;
import com.***.common.exception.DataFlowException;
import com.***.common.exception.ErrorCode;
import com.***.common.exception.InvalidArgumentException;
import com.***.framework.response.RestResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;

/**
 * Description 统一异常处理
 *
 * @author Bob
 * @date 2020/9/25
 **/
@RestControllerAdvice
@Slf4j
public class UnifyExceptionHandler {

    /**
     * @description 业务异常处理
     * @author Bob
     * @date 2020/9/25
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public RestResponse businessException(BusinessException e, HttpServletRequest request) {
        log.error("{} {}, 发生业务异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage());
        return RestResponse.error(e.getErrCode(), e.getMessage());
    }

    /**
     * @description 参数异常处理
     * @author Bob
     * @date 2020/9/25
     */
    @ExceptionHandler(value = InvalidArgumentException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public RestResponse invalidArgumentException(InvalidArgumentException e, HttpServletRequest request) {
        log.error("{} {}, 发生参数异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage());
        return RestResponse.error(e.getErrCode(), e.getMessage());
    }

    /**
     * @description 异常处理
     * @author Bob
     * @date 2020/9/25
     */
    @ExceptionHandler(value = DataFlowException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public RestResponse dataFlowException(DataFlowException e, HttpServletRequest request) {
        log.error("{} {}, 发生异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage());
        return RestResponse.error(e.getErrCode(), e.getMessage());
    }

    /**
     * @description 数据库异常
     * @author Bob
     * @date 2020/9/27
     */
    @ExceptionHandler(DataAccessException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public RestResponse dataAccessException(DataAccessException e, HttpServletRequest request) {
        log.error("{} {}, 发生数据库异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage());
        return RestResponse.error(ErrorCode.DAO_EXCEPTION, e.getMessage());
    }

    /**
     * @description 空指针异常
     * @author Bob
     * @date 2020/9/27
     */
    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public RestResponse nullPointerException(NullPointerException e, HttpServletRequest request) {
        log.error("{} {}, 发生空指针异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage());
        return RestResponse.error(ErrorCode.NULL_POINTER_EXCEPTION, e.getMessage());
    }

    /**
     * @description 其它异常
     * @author Bob
     * @date 2020/9/27
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public RestResponse handleException(Exception e, HttpServletRequest request) {
        // 其他内部spring mvc异常
        RestResponse restResponse = mvcExceptionResolve(e);
        if (restResponse != null) {
            if (HttpStatus.Series.valueOf(Integer.valueOf(restResponse.getCode()).intValue()) == HttpStatus.Series.CLIENT_ERROR) {
                log.warn("{} {}, 发生异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage());
            } else {
                log.error("{} {}, 发生异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage(), e);
            }
            return restResponse;
        }

        log.error("{} {}, 发生异常, 原因:{}", request.getMethod(), request.getRequestURI(), e.getMessage(), e);
        return RestResponse.error(ErrorCode.EXCEPTION, e.getMessage());
    }

    /**
     * @description MVC异常处理
     * @author Bob
     * @date 2020/9/27
     */
    protected RestResponse mvcExceptionResolve(Exception ex) {
        if (ex instanceof NoHandlerFoundException) {
            return RestResponse.error(HttpStatus.NOT_FOUND.toString(), HttpStatus.NOT_FOUND.getReasonPhrase());
        } else if (ex instanceof HttpRequestMethodNotSupportedException) {
            return RestResponse.error(HttpStatus.METHOD_NOT_ALLOWED.toString(), ex.getMessage());
        } else if (ex instanceof HttpMediaTypeNotSupportedException) {
            return RestResponse.error(HttpStatus.UNSUPPORTED_MEDIA_TYPE.toString(), ex.getMessage());
        } else if (ex instanceof HttpMediaTypeNotAcceptableException) {
            return RestResponse.error(HttpStatus.NOT_ACCEPTABLE.toString(), HttpStatus.NOT_ACCEPTABLE.getReasonPhrase());
        } else if (ex instanceof MissingPathVariableException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), "Missing path variable");
        } else if (ex instanceof MissingServletRequestParameterException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof ServletRequestBindingException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof ConversionNotSupportedException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof TypeMismatchException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof HttpMessageNotReadableException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof HttpMessageNotWritableException) {
            return RestResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.toString(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
        } else if (ex instanceof MethodArgumentNotValidException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof MissingServletRequestPartException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof BindException) {
            return RestResponse.error(HttpStatus.BAD_REQUEST.toString(), HttpStatus.BAD_REQUEST.getReasonPhrase());
        } else if (ex instanceof AsyncRequestTimeoutException) {
            return RestResponse.error(HttpStatus.SERVICE_UNAVAILABLE.toString(), HttpStatus.SERVICE_UNAVAILABLE.getReasonPhrase());
        }
        return null;
    }
}

参考:https://blog.youkuaiyun.com/qq_35098526/article/details/88949425

https://blog.youkuaiyun.com/devcloud/article/details/108598751

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值