springBoot 全局异常处理

    科技2022-07-16  93

    前言

    本demo的github链接: https://github.com/heruideid/springBoot_demo/tree/master 我的公众号「Rui的后端手册」,先赞后看,月入百万

    代码分层

    springBoot开发的web项目中,强调分层的概念,一个完整的项目一般会划分出controller层和service层。

    controller层负责**service层业务方法的路由和参数的校验**。应用跑起来后,根据收到请求的url和请求类型调用对应controller层的方法处理。controller层方法首先对接收的参数进行校验,包括非空校验、数值范围校验、长度检验等;接着调用service层的方法处理业务逻辑service层负责编写具体的业务代码,比如操作mysql、消息队列、redis等

    因此,为了代码的可维护性,controller层代码应该尽量简洁,验证一下参数,直接丢给service层处理即可

    简洁的全局异常处理

    异常处理的方式无外乎两种:

    try/catch发现异常,立即处理throw 往上抛出,交给调用者处理

    在springBoot的web项目中可以采用全局异常处理的方式:

    针对项目可能出现的异常,定义一个ServiceException类,该类包括enum类型的变量ExceptionType(代表异常类型)和string类型的变量msg(代表异常信息)

    service层的方法套一个大的try/catch;捕获到异常后,在日志里打印异常,并往controller层抛出新的ServiceException实例;

    controller层方法将ServiceException继续抛出

    定义一个全局异常处理类,接收并统一处理controller层抛出的ServiceException

    而controller层方法的参数检验通过以下方式:

    参数加@Validated注解在全局异常处理类中定义参数检验发生异常时的处理方法

    具体实现

    自定义异常:

    public enum ExceptionType { /** * 自定义异常 */ REDIS_EXCEPTION(1), MYSQL_EXCEPTION(2), ILLEGAL_PARAM(3); final int code; final Level level; ExceptionType(int code){ this.code=code; this.level=Level.WARNING; } ExceptionType(int code,Level level){ this.code=code; this.level=level; } public int getCode(){ return code; } } @Data @AllArgsConstructor @NoArgsConstructor public class ServiceException extends Exception{ protected String msg; protected ExceptionType exceptionType; }

    自定义ErrorVO和CommonJsonResponse,这两个类定义了当出现异常时,返回给调用方的Json数据格式:

    @Data @Slf4j public class ErrorVO { private Date timestamp; private Map<String, String[]> params; private String desc; public ErrorVO(Date timestamp, Map<String, String[]> params, String desc) { this.timestamp = timestamp; this.params = params; this.desc = desc; } @Override public String toString() { try { return new ObjectMapper().writeValueAsString(this); } catch (Exception e) { log.warn("toString error.", e); return String.format("ErrorDTO{timestamp=%s, params=%s, desc='%s'}",timestamp,params,desc); } } } @Data public class CommonJsonResponse<T> { public static final String MSG_SUCCESS = "SUCCESS"; public static final String MSG_NOT_FOUND = "NOT FOUND"; public static final int ERROR_CODE_SUCCESS = 0; public static final int ERROR_CODE_NO_SUCH_OBJECT = 404; /** * 200 OK, 500 internal error, mapping of response code */ private int status; /** * 0 means success */ private int errorCode; /** * SUCCESS or others */ private String msg; /** * the result data, may user info or others */ private T data; public CommonJsonResponse() { } public CommonJsonResponse(int status, int errorCode) { this.status = status; this.errorCode = errorCode; } public CommonJsonResponse(int status, int errorCode, String msg) { this.status = status; this.errorCode = errorCode; this.msg = msg; } public static CommonJsonResponse ok() { CommonJsonResponse res = new CommonJsonResponse(HttpStatus.OK.value(), CommonJsonResponse.ERROR_CODE_SUCCESS); res.setMsg(CommonJsonResponse.MSG_SUCCESS); return res; } public static<T> CommonJsonResponse ok(T t) { CommonJsonResponse res = new CommonJsonResponse(HttpStatus.OK.value(), CommonJsonResponse.ERROR_CODE_SUCCESS); res.setMsg(CommonJsonResponse.MSG_SUCCESS); res.setData(t); return res; } }

    在controller层写一个全局异常处理类,几个关键点:

    在类上加上@ControllerAdvice注解继承ResponseEntityExceptionHandler,并覆写handleMethodArgumentNotValid方法,这是参数检验失败时的处理方法自定义一个全局异常处理方法globalExceptionHandler,并加以@ExceptionHandler(ServiceException.class)注解,作用是处理对应controller层方法抛出的指定类型异常 @RestController @ControllerAdvice @Slf4j public class CustomizedExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(ServiceException.class) public ResponseEntity<CommonJsonResponse> globalExceptionHandler(ServiceException ex, WebRequest request){ ErrorVO errorDetails = new ErrorVO(new Date(), request.getParameterMap(), request.getDescription(false)); ExceptionType exType = ex.getExceptionType(); if(exType==ExceptionType.REDIS_EXCEPTION||exType==ExceptionType.MYSQL_EXCEPTION){ // 500 服务器内部错误 CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), exType.getCode(), ex.getMsg()); response.setData(errorDetails); return new ResponseEntity<CommonJsonResponse>(response, HttpStatus.INTERNAL_SERVER_ERROR); } else if(exType==ExceptionType.ILLEGAL_PARAM){ // 400 客户端参数错误 CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.BAD_REQUEST.value(), exType.getCode(), ex.getMsg()); response.setData(errorDetails); return new ResponseEntity<CommonJsonResponse>(response, HttpStatus.BAD_REQUEST); }else { // catch as 200 CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.OK.value(), exType.getCode(), ex.getMsg()); response.setData(errorDetails); return new ResponseEntity<>(response, HttpStatus.OK); } } @Override public ResponseEntity<Object> handleMethodArgumentNotValid( MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { BindingResult exceptions = ex.getBindingResult(); String message = "参数异常"; if (exceptions.hasErrors()) { List<ObjectError> errors = exceptions.getAllErrors(); if (!errors.isEmpty()) { FieldError fieldError = (FieldError) errors.get(0); message = fieldError.getDefaultMessage(); } } ErrorVO errorDetails = new ErrorVO(new Date(), request.getParameterMap(), request.getDescription(false)); CommonJsonResponse<ErrorVO> response = new CommonJsonResponse<>(HttpStatus.BAD_REQUEST.value(), ExceptionType.ILLEGAL_PARAM.getCode(), message); response.setData(errorDetails); return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } }

    controller层在检验参数时加上@Validated注解,这样在校验参数失败时,会调用全局异常处理类中的handleMethodArgumentNotValid方法

    @RestController @RequestMapping("/MyController") public class MyController { @Autowired private MyService myService; @PostMapping("/test") public void test(@Validated @RequestBody Student student) throws ServiceException { myService.method1(); } }

    service层方法就是打印异常日志并向上抛出ServiceException异常:

    @Service @Slf4j public class MyService { public void method1() throws ServiceException { try{ /** * your service code */ throw new Exception(); }catch (Exception e){ //catch各种异常往上抛出对应ServiceException log.warn("mysql update exception",e); throw new ServiceException("mysql update exception", ExceptionType.MYSQL_EXCEPTION); } } }

    最后的效果图:

    参数非法时:

    参数合法但service层出现异常时:

    Processed: 0.009, SQL: 8