SpringBoot基础之全局异常处理

1,545 阅读4分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

前言

当前端向后端请求数据的时候,正常情况下,接口会返回我们理想的格式.但是在后端程序出现问题的情况下,返回的默认的数据不一定是我们想要的类型,所有当程序出现异常时,我们需要有一个统一的全局异常处理器,将异常数据处理成,我们需要的格式,并返回前台

需要处理异常的位置

(一) 通过controller抛出的异常

该异常是业务运行时导致的异常,在controller发生,或者能向上抛出到controller的异常,这种异常通过自定义异常类并注解@ControllerAdvice或者@RestControllerAdvice即可达到目的:

@RestControllerAdvice
//@ControllerAdvice
@Slf4j
public class AllExceptionHandler{


    @ExceptionHandler(value =CustomException.class)
    public R customExceptionHandle(Exception e){
        //这个地方应该去创建多个Exception类然后分类去捕获
        log.error("自定义异常: {}",e);
        return R.fail(e.getMessage());
    }
    
    @ExceptionHandler(value = org.springframework.validation.BindException.class)
    public R bindExceptionHandle(BindException ex){
        StringBuffer defaultMessage = new StringBuffer();
        List<ObjectError> list = ex.getBindingResult().getAllErrors();
        if(list!=null && list.size()>0){
            try {
                int i=0;
                for (ObjectError error : list) {
                    if(i>0){
                        defaultMessage.append(",");
                    }
                    defaultMessage.append(error.getDefaultMessage());
                    i++;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        String s = defaultMessage.toString();
        log.warn("用户数据参数异常: {}",s);
        return R.fail(s);

    }
    
    @ExceptionHandler(value = Exception.class)
    public R exceptionHandle(Exception e){
        log.error("未知的异常{},{}",e.getMessage(),e);
        return R.fail("系统异常",e.getMessage());
    }

    public AllExceptionHandler() {
        super();
    }

使用注意

  1. @RestControllerAdvice注解,可以用于定义@ExceptionHandler@InitBinder@ModelAttribute,并应用到所有@RequestMapping

  2. @RestControllerAdvice继承了@ControllerAdvice功能的子类,将返回信息写入到Response的Body中(因为也注解了@ResponseBody),因此如果是json交互的前后单分离项目直接用@RestControllerAdvice的更好.

  3. @RestControllerAdvice@ControllerAdvice注解包含一些属性:

    属性解释
    @ControllerAdvice("org.zdc.controllers")捕获指定包中的控制器的异常
    @ControllerAdvice(annotations = RestController.class)捕获带有注解@RestController的控制器的异常
    @ControllerAdvice(assignableTypes = {AbstractController.class})捕获有带有指定签名的控制器异常
  4. 如果不生效,则需要注意观察是否被filter,Aspect等拦截,或者异常不是在扫描的包下抛出的.

  5. 可以定义多个@ExceptionHandler, 当异常出现的时候,程序会从上到下依次进行匹配,匹配成功之后就会终止继续匹配,因此最后一个@ExceptionHandler应该定义为Exception.class

  6. @ExceptionHandler定义的原则: 除了最后一个Exception.class外, 其他的异常则需要按需实现.比如:CustomException.class是业务异常,返回的错误信息,只包含e.getMessage()即可,而@Validated参数验证异常BindException.class,则需要解析异常愿意,好让用户明白具体是哪个参数异常导致的

(二) 拦截404或者服务器错误等未进入controller的异常

当请求了一个未定义的请求或解析请求报错的时候,程序会抛出错误,并把错误转发到/error接口上,然后该返回错误的数据,但是返回的数据格式可能不是我们想要的,所以需要实现一个默认的controller并实现该/error方法.

@RestController
@Slf4j
public class OtherExceptionHandler implements ErrorController {
    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping("/error")
    public R otherError(HttpServletRequest request, HttpServletResponse response) {

        //将所有的错误请求都处理成200 具体根据实际项目配置
        response.setStatus(200);

        Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
        String errorMsg = "出现错误";

        if (statusCode == null) {
            errorMsg = "服务器内部错误";
        }
        
        //转发过来的自定义CustomException异常
        if(statusCode==5001 && request.getAttribute("javax.servlet.error.exception") instanceof CustomException){
            log.warn("Filter异常:{}",((Exception)request.getAttribute("javax.servlet.error.exception")));
            return R.fail(((Exception)request.getAttribute("javax.servlet.error.exception")).getMessage());
        }

        try {
            return R.fail(errorMsg, statusCode, HttpStatus.valueOf(statusCode));
        } catch (Exception ex) {
            errorMsg = "服务器内部错误:" + statusCode;
        }
        log.error("服务器错误,错误码:{}",statusCode);
        return R.fail(errorMsg);
    }
}

看到这里你应该看到了转发过来的自定义CustomException异常,实际上我们可以手动将异常转发到这里(比如自定义filter中), 使用方法请文章全局搜索手动转发到/error接口

(三) filter等自定义代码中异常

在自定义的filter中,业务判断不符的时候需要中断操作抛出异常、 在自定义的Aspect中,代理方法报错需要抛出异常,等等,一次我们需要注意处理这里的异常

filter中使用

@Component
public class ZdcFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {


    if(false){
    //中断 抛出异常
    
    //第一种  手动转发到/error接口
    request.setAttribute("javax.servlet.error.status_code",5001);
    request.setAttribute("javax.servlet.error.status_msg","通过转发/error设置返回数据");
    request.getRequestDispatcher("/error").forward(request, response);
    return;
         
    //第二种 抛出异常,自动转到/error接口,http状态码500
     throw new CustomException("通过MyFilter,抛异常返回值");
      
    //第三种 直接写到response中返回
    response.setContentType("application/json; charset=utf-8");
    response.getWriter().print("通过MyFilter直接接调用response返回数据");
    response.getWriter().flush();
    response.getWriter().close();
    return;
    
     }else{
      //正常通过
      chain.doFilter(request,response);
     }

    }
}

当然在这里我们基本上都时使用第三种方式直接通过response写回去.

    作者:ZOUZDC
    链接:https://juejin.cn/post/7028963866063306760
    来源:稀土掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。