SSM 全局异常统一处理

Exisi 2022-06-28 14:51:04
Categories: > Tags:
  • 如果要统一处理项目的异常,就需要分析产生异常的类别,针对相同逻辑的异常进一步处理,项目的异常可以大概分为以下类别:

类别

异常类

说明

处理方案

业务异常

BusinessException

规范的用户行为产生的异常

不规范的用户行为操作产生的异常

发送对应消息传递给用户,提醒规范操作

系统异常

SystemException

项目运行过程中可预计且无法避免的异常

记录日志

发送固定消息传递给用户,安抚用户

发送特定消息给运维人员,提醒维护

其他异常

Exception

编程人员未预期到的异常

记录日志

发送固定消息传递给用户,安抚用户

发送特定消息给运维人员,提醒维护

(纳入预期范围内)

 

  • 为了清晰的定义异常,首先需要自定义相关的异常类,自定义异常类建议继承 RuntimeException。因为 RuntimeException 是一种非检查型异常,所以在程序中抛出这种异常时不需要显式地声明或捕获它们。

 

  • 这意味着,如果自定义的异常类继承了 RuntimeException,那么在程序中抛出该异常时就不需要使用 try-catch 块来捕获它,而可以直接将其抛出进行异常处理,这样可以使代码更加简洁、易读。

示例

  • SystemException.java

public class SystemException extends RuntimeException{

    private ResultCode resultCode;

 

    public SystemException(ResultCode resultCode) {

super(resultCode.getMsg());

        this.resultCode = resultCode;

    }

 

    public SystemException(String message, ResultCode resultCode) {

        super(message);

        this.resultCode = resultCode;

    }

 

    public SystemException(String message, Throwable cause, ResultCode resultCode) {

        super(message, cause);

        this.resultCode = resultCode;

    }

 

    public ResultCode getResultCode() {

        return resultCode;

    }

 

    public void setResultCode(ResultCode resultCode) {

        this.resultCode = resultCode;

    }

}

 

  • BusinessException.java

public class BusinessException extends RuntimeException {

    private ResultCode resultCode;

 

    public BusinessException(ResultCode resultCode) {

super(resultCode.getMsg());

        this.resultCode = resultCode;

    }

 

    public BusinessException(String message, ResultCode resultCode) {

        super(message);

        this.resultCode = resultCode;

    }

 

    public BusinessException(String message, Throwable cause, ResultCode resultCode) {

        super(message, cause);

        this.resultCode = resultCode;

    }

 

    public ResultCode getResultCode() {

        return resultCode;

    }

 

    public void setResultCode(ResultCode resultCode) {

        this.resultCode = resultCode;

    }

}

 

  • 同时需要将具体异常对应的错误码添加到枚举类,具体业务的异常可以自定义,以下业务异常和系统异常的错误码示例

示例

public enum Code {

 

    //其他错误码

    /** 业务错误 */

    BUSINESS_ERROR(40001,"业务错误"),

 

    /** 业务超时错误 */

    BUSINESS_TIMEOUT_ERROR(40002,"业务超时错误"),

 

    /** 业务未知错误 */

    BUSINESS_UNKNOW_ERROR(40003,"业务未知错误"),

 

    /** 系统错误 */

    SYSTEM_ERROR(50001,"系统错误"),

 

    /** 系统超时错误 */

    SYSTEM_TIMEOUT_ERROR(50002,"系统超时错误"),

 

    /** 系统未知错误 */

    SYSTEM_UNKNOW_ERROR(50004,"系统未知错误");

 

    //get方法和构造方法

}

 

  • Spring 中,统一处理异常有 2 种方式,使用 @ExceptionHandler@ControllerAdvice@RestControlleradvice) 注解或自定义实现 HandlerExceptionResolver 接口

 

1. 注解方式

@ExceptionHandler + @ControllerAdvice@RestControllerAdvice

 

2. 接口方式

实现 HandlerExceptionResolver 接口

 

 

 

注解方式

  • 定义好特定的错误处理类后,只需要在 @RestControllerAdvice 注解的类下集中处理错误,这时我们可以根据错误添加一些错误处理方案,并对错误内容进行数据的封装,把错误返回给前端

示例

@RestControllerAdvice

public class GlobalExceptionAdvice {

 

    @ExceptionHandler(BusinessException.class)

    @ResponseStatus(HttpStatus.BAD_REQUEST)

    public Result<String> doBusinessException(BusinessException ex) {

        return Result.fail(ex.getResultCode(), ex.getMessage(), null);

    }

 

    @ExceptionHandler(SystemException.class)

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

    public Result<String> doSystemException(SystemException ex) {

        //记录日志

        //发送特定消息给运维人员,提醒维护

        return Result.fail(ex.getResultCode(), ex.getMessage(), null);

    }

 

    @ExceptionHandler(Exception.class)

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)

    public Result<String> doOtherException(Exception ex) {

        //记录日志

        //发送特定消息给运维人员,提醒维护

        return Result.fail(ResultCode.SYSTEM_UNKNOW_ERROR, ex.getMessage(), null);

    }

}

 

  • 当发生错误时,只需要在 Controller 层中抛出异常到 GlobalExceptionAdvice 就完成了异常处理,以下以模拟实际错误为例

示例

@RestController

@RequestMapping("/books")

public class BookController {

    @Resource

    private BookService bookService;

    

    @PostMapping

    public Result<>  save(@RequestBody Book book) {

       //模拟业务异常,包装成自定义异常

       if(book.name == "Spring实战 第5"){

           throw new BusinessException("数据已存在,请勿重复提交", ResultCode.BUSINESS_ERROR);

       }

       //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常

       try{

           int i = 1/0;

       }catch (Exception e){

           throw new SystemException("服务器访问超时,请重试", ResultCodeCode.SYSTEM_TIMEOUT_ERROR);

       }

       boolean flag = bookService.save(book);

        if (!flag){

            return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, ResultCode.INTERNAL_SERVER_ERROR.getMsg(), flag);

        }

        return Result.success(flag);

    }

}

 

接口数据返回如下:

 

 

 

接口方式

  • SpringMVC 中提供的 HandlerExceptionResolver 接口可以实现全局异常处理器,通过实现此接口可以对异常进行处理

示例

@Component

public class GlobalExceptionHandler implements HandlerExceptionResolver {

 

    @Override

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {

                response.setContentType(MediaType.APPLICATION_JSON_VALUE);

        response.setCharacterEncoding("UTF-8");

        response.setHeader("Cache-Control", "no-cache, must-revalidate");

 

        Result<String> result;

 

        if (ex instanceof BusinessException) {

    response.setStatus(HttpStatus.BAD_REQUEST.value());

            result = new Result<>(ResultCode.BUSINESS_ERROR,ex.getMessage(),null);

        }

        else if (ex instanceof SystemException) {

    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());  

            result = new Result<>(ResultCode.SYSTEM_ERROR,ex.getMessage(),null);

        }

        else {

    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());

            result = new Result<>(ResultCode.INTERNAL_SERVER_ERROR,"未知错误",null);

        }

 

        try {

            ObjectMapper objectMapper = new ObjectMapper();

            objectMapper.writeValue(response.getWriter(), result);

 

            return new ModelAndView();

        } catch (IOException e) {

            e.printStackTrace();

        }

        return null;

    }

}

 

  • 当发生错误时,只需要在 Controller 层中抛出异常就完成了异常处理,以下以模拟实际错误为例

示例

@RestController

@RequestMapping("/books")

public class BookController {

    @Resource

    private BookService bookService;

    

    @PostMapping

    public Result<>  save(@RequestBody Book book) {

       //模拟业务异常,包装成自定义异常

       if(book.name == "Spring实战 第5"){

           throw new BusinessException("数据已存在,请勿重复提交", ResultCode.BUSINESS_ERROR);

       }

       //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常

       try{

           int i = 1/0;

       }catch (Exception e){

           throw new SystemException("服务器访问超时,请重试", ResultCodeCode.SYSTEM_TIMEOUT_ERROR);

       }

       boolean flag = bookService.save(book);

        if (!flag){

            return Result.fail(ResultCode.INTERNAL_SERVER_ERROR, ResultCode.INTERNAL_SERVER_ERROR.getMsg(), flag);

        }

        return Result.success(flag);

    }

}

 

接口数据返回如下:

实现 HandlerExceptionResolver 接口处理全局异常的方式并不被推荐,很明显这种处理方式不够灵活,异常全部写在一个方法中,同时需要对所有异常进行判断,难以对代码进行维护和扩展,代码耦合度高

 

 

 

来自 <https://juejin.cn/post/7159555171616292871>