欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

SpringBoot中的全局异常处理

程序员文章站 2023-03-09 15:15:42
在实际项目中,如果出现了异常,我们不希望直接把异常抛给用户,应该对异常进行处理,然后返回一个友好的信息给用户。这节主要总结一下项目中如何使用SpringBoot如何拦截全局的异常。 1. 定义返回的json结构 请求接口需要返回json数据,一般后台会统一定义一个返回给前端的数据结构,包括code、 ......

在实际项目中,如果出现了异常,我们不希望直接把异常抛给用户,应该对异常进行处理,然后返回一个友好的信息给用户。这节主要总结一下项目中如何使用SpringBoot如何拦截全局的异常。

1. 定义返回的json结构

请求接口需要返回json数据,一般后台会统一定义一个返回给前端的数据结构,包括code、msg信息等,这可以参考【1】SpringBoot返回Json数据及封装中封装的统一json结构,如下:

public class JsonResult {
    /**
     * 异常码
     */
    protected String code;

    /**
     * 异常信息
     */
    protected String msg;

    protected JsonResult() {}
    
    public JsonResult(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    // get set
}

 

2. 处理系统异常

新建一个GlobalExceptionHandler全局异常处理类,然后加上@ControllerAdvice注解即可拦截项目中抛出的异常,该注解中也可以跟上basePackages属性,用来指定拦截哪个包中的异常,一般我们可以不指定,所有异常都拦截。

@ControllerAdvice
public class GlobalExceptionHandler {
    
}

 

下面举几个例子来说明一下如何使用。

2.1 处理不合法的请求格式异常

有些时候,请求格式不合法,会抛出TypeMismatchException,我们可以拦截该异常,做一个友好处理:

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    /**
     * 不合法的请求格式异常
     * @param ex
     * @return
     */
    @ExceptionHandler(TypeMismatchException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JsonResult handleTypeMismatchException(TypeMismatchException ex) {
        logger.error("不合法的请求格式", ex);
        return new JsonResult("400", "不合法的请求格式");
    }
}

 

2.2 HTTP参数不可读异常

有些时候,请求参数不合法,会抛出HttpMessageNotReadableException,我们可以拦截该异常,做一个友好处理:

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    /**
     * HTTP参数不可读
     * @param ex
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public JsonResult handleHttpMessageNotReadableException(
            HttpMessageNotReadableException ex) {
        logger.error("请求参数不可读", ex);
        return new JsonResult("400", "请求参数不可读");
    }
}

 

2.3 一劳永逸?

当然了,异常很多,比如还有RuntimeException,数据库还有一些查询或者操作异常等等。由于Exception异常是父类,所有异常都会继承该异常,所以我们可以直接拦截Exception异常:

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    /**
     * 系统异常 预期以外异常
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public JsonResult handleUnexpectedServer(Exception ex) {
        logger.error("系统异常:", ex);
        return new JsonResult("500", "系统发生异常,请联系管理员");
    }
}

 

但是项目中,我们一般都会比较详细的去拦截一些常见异常,拦截Exception虽然可以一劳永逸,但是不利于我们去排查或者定位问题。实际项目中,可以把拦截Exception异常写在GlobalExceptionHandler最下面,如果都没有找到,最后再拦截一下Exception异常,保证输出信息友好。

3. 拦截自定义异常

在实际项目中,除了拦截一些系统异常外,在某些业务上,我们需要自定义一些异常,比如在微服务中,服务之间的相互调用很平凡,很常见。要处理一个服务的调用,那么可能会调用失败,此时我们需要自定义一个异常,当调用失败时抛出来,给GlobalExceptionHandler去捕获。

3.1 定义异常信息

由于在业务中,有很多异常,针对不同的业务,可能给出的提示信息不同,所以为了方便项目管理,我们一般会定义一个异常信息枚举类。

/**
 * 业务异常提示信息枚举类
 * @author shengwu ni
 */
public enum BusinessMsgEnum {
    /** 参数异常 */
    PARMETER_EXCEPTION("102", "参数异常!"),
    /** 等待超时 */
    SERVICE_TIME_OUT("103", "服务调用超时!"),
    /** 参数过大 */
    PARMETER_BIG_EXCEPTION("102", "输入的图片数量不能超过50张!"),
    /** 500 : 一劳永逸的提示也可以在这定义 */
    UNEXPECTED_EXCEPTION("500", "系统发生异常,请联系管理员!");
    // 还可以定义更多的业务异常

    /**
     * 消息码
     */
    private String code;
    /**
     * 消息内容
     */
    private String msg;

    private BusinessMsgEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    // set get方法
}

 

3.2 拦截自定义异常

然后我们定义一个业务异常,当出现业务异常时,我们就抛这个自定义的业务异常即可。

/**
 * 自定义业务异常
 * @author shengwu ni
 */
public class BusinessErrorException extends RuntimeException {
    
    private static final long serialVersionUID = -7480022450501760611L;

    /**
     * 异常码
     */
    private String code;
    /**
     * 异常提示信息
     */
    private String message;

    public BusinessErrorException(BusinessMsgEnum businessMsgEnum) {
        this.code = businessMsgEnum.code();
        this.message = businessMsgEnum.msg();
    }
    // get set方法
}

 

构造方法中,传入我们上面自定义的异常枚举类,所以在项目中,如果有信息异常信息,我们直接在枚举类中添加即可,然后再拦截该异常时获取即可。

@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
    /**
     * 拦截业务异常,返回业务异常信息
     * @param ex
     * @return
     */
    @ExceptionHandler(BusinessErrorException.class)
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    public JsonResult handleBusinessError(BusinessErrorException ex) {
        String code = ex.getCode();
        String message = ex.getMessage();
        return new JsonResult(code, message);
    }
}

 

在业务代码中,我们可以直接在抛出业务异常,测试一下:

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping("/exception")
    public String testException() {
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            throw new BusinessErrorException(BusinessMsgEnum.UNEXPECTED_EXCEPTION);
        }
        return null;
    }
}

 

运行一下项目,测试一下,返回json如下;

{"code":"500","msg":"系统发生异常,请联系管理员!"}

 

SpringBoot的全局异常拦截处理就总结这么多,在项目中运用也很广泛,基本上每个项目中都需要全局异常处理。