[springboot] Universal global exception handling logic (with video)

Generic global exception handling logic

1. General exception handling logic

The programmer's exception handling logic should be very simple: no matter in the Controller layer, the Service layer or any other location, the programmer is only responsible for one thing: that is to catch the exception and convert the exception into a custom exception. Populate the CustomException message with user-friendly information and throw the CustomException.

@Service
public class ExceptionService {
    
    

    //服务层,模拟系统异常
    public void systemBizError() {
    
    
        try {
    
    
            Class.forName("com.mysql.jdbc.xxxx.Driver");
        } catch (ClassNotFoundException e) {
    
    
            throw new CustomException(
                    CustomExceptionType.SYSTEM_ERROR,
                    "在XXX业务,myBiz()方法内,出现ClassNotFoundException,请将该信息告知管理员");
        }
    }

    //服务层,模拟用户输入数据导致的校验异常
    public void userBizError(int input)  {
    
    
        if(input < 0){
    
     //模拟业务校验失败逻辑
            throw new CustomException(
                    CustomExceptionType.USER_INPUT_ERROR,
                    "您输入的数据不符合业务逻辑,请确认后重新输入!");
        }

        //…… 其他的业务
    }

}

2. Global exception handler

Through the requirements of the coding standards within the team, we already know that the programmer is not allowed to intercept and handle Exception, and the exception must be converted into a custom exception CustomException and thrown out. So who will handle the exception after the programmer runs out of it? That is ControllerAdvice.
The role of the ControllerAdvice annotation is to monitor all Controllers. Once the Controller throws a CustomException, the exception will be processed in the method annotated with @ExceptionHandler(CustomException.class). The processing method is very simple to return it to the front end using AjaxResponse.error(e)a general interface data structure packaged.

@ControllerAdvice
public class WebExceptionHandler {
    
    

    //处理程序员主动转换的自定义异常
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public AjaxResponse customerException(CustomException e) {
    
    
        if(e.getCode() == CustomExceptionType.SYSTEM_ERROR.getCode()){
    
    
                 //400异常不需要持久化,将异常信息以友好的方式告知用户就可以
                //TODO 将500异常信息持久化处理,方便运维人员处理
        }
        return AjaxResponse.error(e);
    }

    //处理程序员在程序中未能捕获(遗漏的)异常
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public AjaxResponse exception(Exception e) {
    
    
        //TODO 将异常信息持久化处理,方便运维人员处理

        return AjaxResponse.error(new CustomException(
                CustomExceptionType.OTHER_ERROR));
    }


}

3. Test it

Just find an API, inject ExceptionService to access and test it
insert image description here

When id=1 data response result
insert image description here

When id!=1 data response result
insert image description here

4. The business status is consistent with the HTTP protocol status

I don't know if you have noticed a problem (see the picture above)? The problem is that the code of our AjaxResponse is 400, but the real HTTP protocol status code is 200?
In other words, currently

  • The code of AjaxResponse is 400, which represents the business status, which means that the user's request business failed.
  • But the HTTP request is successful, that is to say the data is returned normally.

When many companies develop RESTful services, the HTTP status code is required to reflect the final execution status of the business. Therefore, it is necessary for us to make the business status consistent with the HTTP protocol Response status code.

@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    
    
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    
    
        //return returnType.hasMethodAnnotation(ResponseBody.class);
        return true;
    }
    @Override
    public Object beforeBodyWrite(Object body,
                                  MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class selectedConverterType,
                                  ServerHttpRequest request,
                                  ServerHttpResponse response) {
    
    

        //如果响应结果是JSON数据类型
        if(selectedContentType.equalsTypeAndSubtype(
                MediaType.APPLICATION_JSON)){
    
    
                //为HTTP响应结果设置状态码,状态码就是AjaxResponse的code,二者达到统一
                response.setStatusCode(
                        HttpStatus.valueOf(((AjaxResponse) body).getCode())
                );
                return body;
        }
        return body;
    }
}

The role of implementing the ResponseBodyAdvice interface is to do the last step of processing before returning the data to the user. That is to say, the processing of ResponseBodyAdvice is behind the global exception handling.

The effect is as follows:
insert image description here

5. Further optimization

We already know that the role of the ResponseBodyAdvice interface is to do the last step of processing before returning the data to the user. Optimize the code of the beforeBodyWrite method in the GlobalResponseAdvice above as follows.

insert image description here

  • If the result body of the Controller or global exception handling response is AjaxResponse, it will be returned directly to the front end.
  • If the result body of the Controller or global exception handling response is not AjaxResponse, encapsulate the body as AjaxResponse and then return it to the front end.

So, our previous code is written like this, for example: a controller method returns a value

return AjaxResponse.success(objList);

It can be written like this now, because it will be uniformly encapsulated as AjaxResponse in GlobalResponseAdvice.

return objList;

The final code is as follows:

@Component
@ControllerAdvice
public class GlobalResponseAdvice implements ResponseBodyAdvice {
    
    

  @Override
  public boolean supports(MethodParameter methodParameter, Class aClass) {
    
    
    return true;
  }

  @Override
  public Object beforeBodyWrite(Object body,
                                MethodParameter methodParameter,
                                MediaType mediaType,
                                Class aClass,
                                ServerHttpRequest serverHttpRequest,
                                ServerHttpResponse serverHttpResponse) {
    
    
    //如果响应结果是JSON数据类型
    if(mediaType.equalsTypeAndSubtype(
            MediaType.APPLICATION_JSON)){
    
    
      if(body instanceof AjaxResponse){
    
    
        AjaxResponse ajaxResponse = (AjaxResponse)body;
        if(ajaxResponse.getCode() != 999){
    
     //999 不是标准的HTTP状态码,特殊处理
          serverHttpResponse.setStatusCode(HttpStatus.valueOf(
                  ajaxResponse.getCode()
          ));
        }
        
        return body;
      }else{
    
    
        serverHttpResponse.setStatusCode(HttpStatus.OK);
        return AjaxResponse.success(body);
      }

    }


    return body;
  }
}

Guess you like

Origin blog.csdn.net/hanxiaotongtong/article/details/122892972