第10课:SpringBoot《全局异常处理器》

一、本课程目标:

  1. 弄懂为什么springboot需要《全局异常处理器》?
  2. 编码实战一个springboot《全局异常处理器》
  3. 封装一个自定义异常 ,并集成进《局异常处理器》
  4. 把《全局异常处理器》集成进《接口返回值统一标准格式》

二、springboot为什么需要全局异常处理器?

  1. 先讲下什么是全局异常处理器?

    全局异常处理器就是把整个系统的异常统一自动处理,程序员可以做到不用写try...catch
  2. 那为什么需要全局异常呢?
  • 第一个原因:不用强制写try-catch,由全局异常处理器统一处理
    以下模拟一个Controller的runtime异常
@PostMapping(value="/error1")
public void  error1(  ){
    int i=9/0;
}

int i=9/0 如果不用try-catch捕获的话,客户端就会收到如下异常

{
  "timestamp": "2019-09-08T02:54:27.415+0000",
  "status": 500,
  "error": "Internal Server Error",
  "message": "/ by zero",
  "path": "/user/error1"
}

返回这种格式,明显不友好,故,我们可以用全局异常处理器来统一处理。

  • 第二个原因:自定义异常,只能用全局异常来捕获
    例如:如下代码,我们抛出一个RuntimeException,在这种情况下,只能交给全局异常处理器来处理了
@PostMapping(value="/error4")
public void  error4(  ){
    throw new RuntimeException("用户已存在!!");
}
  • 第三个原因:JSR303规范的Validator参数校验器,参数校验不通过会抛异常,是无法使用try-catch语句直接捕获,只能使用全局异常处理器了
    JSR303规范的Validator参数校验器的异常处理后面课程会单独讲解,本节课暂不讲解。

三、编码实现一个springboot全局异常处理器

步骤1:封装异常内容,统一存在枚举类中

public enum ResultCode {

    /* 成功状态码 */
    SUCCESS(0, "成功"),

    /* 系统500错误*/
    SYSTEM_ERROR(10000, "系统异常,请稍后重试"),

    /* 参数错误:10001-19999 */
    PARAM_IS_INVALID(10001, "参数无效"),

    /* 用户错误:20001-29999*/
    USER_HAS_EXISTED(20001, "用户名已存在");

    private Integer code;

    private String message;

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer code() {
        return this.code;
    }

    public String message() {
        return this.message;
    }
    
}

步骤2:封装Controller的异常结果response


@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ErrorResult {

    /**
     * 异常状态码
     */
    private Integer status;

    /**
     * 用户看得见的异常,例如 用户名重复!!,
     */
    private String message;

    /**
     * 异常的名字
     */
    private String exception;

    /**
     * 异常堆栈的精简信息
     */
    private Object errors;


    public static ErrorResult fail(ResultCode resultCode, Throwable e,String message) {
        ErrorResult result = ErrorResult.fail(resultCode, e);
        result.setMessage(message);
        return result;
    }

    public static ErrorResult fail(ResultCode resultCode, Throwable e) {

        ErrorResult result = new ErrorResult();
        result.setMessage(resultCode.message());
        result.setStatus(resultCode.code());
        result.setException(e.getClass().getName());
        result.setErrors(e.getStackTrace());
        return result;
    }
}

步骤3:加个全局异常处理器,对异常进行处理

@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler  {

    /**
     * 处理运行时异常
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public ErrorResult handleThrowable(Throwable e, HttpServletRequest request) {
        //TODO 运行是异常,可以在这里记录,用于发异常邮件通知
        ErrorResult error =ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
        log.error("URL:{} ,系统异常: ",request.getRequestURI(), e);
        return error;
    }
}

handleThrowable方法的作用是:捕获运行时异常,并把异常统一封装为ErrorResult对象。
以上有几个细节点我们要单独讲解:

  1. @ControllerAdvice在《response统一格式封装》课程的时候,我们就讲解过它是增强Controller的扩展功能。而全局异常处理器,就是扩展功能之一。
  2. @ExceptionHandler统一处理某一类异常,从而能够减少代码重复率和复杂度,@ExceptionHandler(Throwable.class)指处理Throwable的异常。
  3. @ResponseStatus指定客户端收到的http状态码,这里配置500错误,客户端就显示500错误,

步骤4:体验效果

@RestController
@RequestMapping("/user")
public class UserController {
    @PostMapping(value="/error1")
    public void  error1(  ){
        int i=9/0;
    }
}

在浏览器输入 http://127.0.0.1:9090/user/error1 得到以下结果

{
  "status": 10000,
  "message": "系统异常,请稍后重试",
  "exception": "java.lang.ArithmeticException"
}

四、编码实现一个springboot自定义异常

步骤1.封装一个自定义异常

自定义异常通常集成RuntimeException,代码很简单不细讲。

@Data
public class BusinessException extends RuntimeException {

    protected Integer code;

    protected String message;

    public BusinessException(ResultCode resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

}

步骤2.把自定义异常 集成 进全局异常处理器

全局异常处理器只要在上文的基础上,增加一个自定义异常处理方法即可,代码很简单,如下:

@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler  {

    /** 处理自定义异常 */
    @ExceptionHandler(BusinessException.class)
    public ErrorResult handleBusinessException(BusinessException e, HttpServletRequest request) {
        ErrorResult error = ErrorResult.builder().status(e.code)
                .message(e.message)
                .exception(e.getClass().getName())
                .build();
        log.warn("URL:{} ,业务异常:{}", request.getRequestURI(),error);
        return error;
    }
    
    /**
     * 处理运行时异常
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Throwable.class)
    public ErrorResult handleThrowable(Throwable e, HttpServletRequest request) {
        //TODO 运行是异常,可以在这里记录,用于发异常邮件通知
        ErrorResult error =ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
        log.error("URL:{} ,系统异常: ",request.getRequestURI(), e);
        return error;
    }
}

步骤3.体验效果

在ResultCode枚举类中加一个枚举,USER_HAS_EXISTED(20001, "用户名已存在")。

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @PostMapping(value="/error3")
    public void  error3(  ){
        throw new BusinessException(ResultCode.USER_HAS_EXISTED);
    }
}

在浏览器输入 http://127.0.0.1:9090/user/error3 得到以下结果

{
  "status": 20001,
  "message": "用户名已存在",
  "exception": "com.agan.boot.exceptions.BusinessException"
}

五、把“全局异常处理器”集成进“接口返回值统一标准格式”

步骤1.改造ResponseHandler代码

我们要改造《接口返回值统一标准格式》的代码,因为我们在讲解《接口返回值统一标准格式》的时候,就没有处理异常。

@ControllerAdvice(basePackages = "com.agan.boot")
public class ResponseHandler implements ResponseBodyAdvice {

    /**
     * 是否支持advice功能
     * treu=支持,false=不支持
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class> aClass) {
        return true;
    }

    /**
     *
     * 处理response的具体业务方法
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (o instanceof ErrorResult) {
            ErrorResult errorResult = (ErrorResult) o;
            return Result.fail(errorResult.getStatus(),errorResult.getMessage());
        } else if (o instanceof String) {
            return JsonUtil.object2Json(Result.suc(o));
        }
        return Result.suc(o);
    }
}
 
 

以上代码是在《接口返回值统一标准格式》的基础上增加了

if (o instanceof ErrorResult) {
  ErrorResult errorResult = (ErrorResult) o;
  return Result.fail(errorResult.getStatus(),errorResult.getMessage());
} 

其实封装很简单,就是把ErrorResult对象转换Result对象即可。

步骤2.体验效果

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @PostMapping(value="/error3")
    public void  error3(  ){
        throw new BusinessException(ResultCode.USER_HAS_EXISTED);
    }
}

在浏览器输入 http://127.0.0.1:9090/user/error3 得到以下结果

{
  "status": 20001,
  "desc": "用户名已存在",
  "data": null
}

六:课后练习题

自己手写一个《全局异常处理器》和《接口返回值统一标准格式》

1.模拟一个空指针异常,然后返回以下接口返回值统一标准格式:

{
  "code": 10000,
  "msg": "系统异常请稍后...",
  "data": null
}

2.模拟用户登录,提示以下内容

{
  "code": 10000,
  "msg": "用户名或密码错误,请重试",
  "data": null
}

配套学习资料

  1. 课后练习作业请提交到QQ群(1号QQ群3000人已满,请加2号群:985378659[群名:SpringBoot架构师])
  2. 本课程配套免费视频教程
    https://study.163.com/course/introduction/1004576013.htm?share=1&shareId=1016481220
  3. 本课程配套源码地址:https://github.com/agan-java/agan-boot
第10课:SpringBoot《全局异常处理器》_第1张图片
image

你可能感兴趣的:(第10课:SpringBoot《全局异常处理器》)