博客
关于我
@ControllerAdvice+@ExceptionHandler全局处理Controller层异常 及其 原理
阅读量:800 次
发布时间:2023-01-23

本文共 6522 字,大约阅读时间需要 21 分钟。

@ControllerAdvice + @ExceptionHandler全局处理Controller层异常

前言

在设计与数据库相关的Spring MVC项目时,通常会将事务配置在Service层。当数据库操作失败时,Service层会抛出运行时异常,Spring事物管理器进行回滚。这样一来,Controller层就不得不进行try-catch处理Service层的异常,否则会返回不友好的错误信息给客户端。

然而,Controller层每个方法都需要手动处理Service层及数据校验的异常,这种做法非常冗余且不好维护。例如以下代码:

@Controllerpublic class MyController {    @PostMapping("/")    AppResponse add(@Validated(Add.class) @RequestBody Dog dog, Errors errors) {        AppResponse resp = new AppResponse();        try {            // 数据校验            BSUtil.controllerValidate(errors);            // 执行业务            Dog newDog = dogService.save(dog);            // 返回数据            resp.setData(newDog);        } catch (BusinessException e) {            LOGGER.error(e.getMessage(), e);            resp.setFail(e.getMessage());        } catch (Exception e) {            LOGGER.error(e.getMessage(), e);            resp.setFail("操作失败!");        }        return resp;    }}

本文将使用@ControllerAdvice和@ExceptionHandler来实现全局的Controller层异常处理,避免了重复代码,并统一处理Service层及数据校验异常。

优缺点

  • 优点:统一处理Controller层未捕获的异常,减少代码冗余,提升可维护性和扩展性。
  • 缺点:无法处理Interceptor层或Spring框架层的异常。

基本使用示例

1. 定义全局异常处理类

在GlobalExceptionHandler类中使用@ControllerAdvice注解:

@ControllerAdvicepublic class GlobalExceptionHandler {    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);    @ExceptionHandlerExceptio

2. 声明异常处理方法

使用@ExceptionHandler注解声明具体异常处理方法:

@ControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(Exception.class)    @ResponseBody    String handleException() {        return "Exception Deal!";    }}

支持多种异常类型:

@ControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(BusinessException.class)    @ResponseBody    AppResponse handleBusinessException(BusinessException e) {        LOGGER.error(e.getMessage(), e);        AppResponse response = new AppResponse();        response.setFail(e.getMessage());        return response;    }}

处理Service层异常

示例代码

封装的业务异常类:

public class BusinessException extends RuntimeException {    public BusinessException(String message) {        super(message);    }}

Service实现类:

@Service@Transactionalpublic class DogService {    @Transactional    public Dog update(Dog dog) {        // 数据校验逻辑        BSUtil.isTrue(false, "狗狗名字已经被使用了...");        // 更新数据库信息        return dog;    }}

辅助工具类:

public static void isTrue(boolean expression, String error) {    if (!expression) {        throw new BusinessException(error);    }}

GlobalExceptionHandler处理业务异常:

@ControllerAdvicepublic class GlobalExceptionHandler {    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);    @ExceptionHandler(BusinessException.class)    @ResponseBody    AppResponse handleBusinessException(BusinessException e) {        LOGGER.error(e.getMessage(), e);        AppResponse response = new AppResponse();        response.setFail(e.getMessage());        return response;    }}

Controller层无需手动处理异常:

@RestController@RequestMapping("/dogs")public class DogController {    private DogService dogService;    @PatchMapping("")    Dog update(@Validated(Update.class) @RequestBody Dog dog) {        return dogService.update(dog);    }}

代码说明

  • Logger对所有异常进行日志记录。
  • @ExceptionHandler(BusinessException.class)处理业务异常,获取错误信息并返回给客户端。
  • @ExceptionHandler(Exception.class)作为兜底处理,返回统一错误信息。

数据校验异常处理

校验规则

Dog类数据校验:

@Datapublic class Dog {    @NotNull(message = "{Dog.id.non}", groups = {Update.class})    @Min(value = 1, message = "{Dog.age.lt1}", groups = {Update.class})    private Long id;    @NotBlank(message = "{Dog.name.non}", groups = {Add.class, Update.class})    private String name;    @Min(value = 1, message = "{Dog.age.lt1}", groups = {Add.class, Update.class})    private Integer age;}

全局处理数据校验异常

在GlobalExceptionHandler中增加处理MethodArgumentNotValidException:

@ControllerAdvicepublic class GlobalExceptionHandler {    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);    @ExceptionHandler(MethodArgumentNotValidException.class)    @ResponseBody    AppResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {        LOGGER.error(e.getMessage(), e);        AppResponse response = new AppResponse();        response.setFail(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());        return response;    }}

异常处理机制(三种方式)

参考文章如链接:

异常处理机制三种方式

  • 第一种思路:设计一个基类
  • 通过继承实现:

    public class BaseController {    @ExceptionHandler    @ResponseBody    public Object expHandler(Exception e) {        if (e instanceof SystemException) {            SystemException ex = (SystemException) e;            return WebResult.buildResult().status(ex.getCode()).msg(ex.getMessage());        } else {            e.printStackTrace();            return WebResult.buildResult().status(Config.FAIL).msg("系统错误");        }    }}

    所有Controller继承BaseController。

    1. 第二种思路:使用接口的默认方法
    2. public interface DataExceptionSolver {    @ExceptionHandler    @ResponseBody    default Object exceptionHandler(Exception e) {        try {            throw e;        } catch (SystemException systemException) {            systemException.printStackTrace();            return WebResult.buildResult().status(systemException.getCode()).msg(systemException.getMessage());        } catch (Exception e1) {            e1.printStackTrace();            return WebResult.buildResult().status(Config.FAIL).msg("系统错误");        }    }}
      1. 第三种思路:使用加强Controller做全局异常处理
      2. @ControllerAdvicepublic class GlobalExceptionHandler {    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);    @ExceptionHandler(SystemException.class)    @ResponseBody    public Object customHandler(SystemException e) {        e.printStackTrace();        return WebResult.buildResult().status(e.getCode()).msg(e.getMessage());    }    @ExceptionHandler(Exception.class)    @ResponseBody    public Object exceptionHandler(Exception e) {        e.printStackTrace();        return WebResult.buildResult().status(Config.FAIL).msg("系统错误");    }}

        这样,Controller方法可以极简:

        @RestController@RequestMapping("passport")public class PassportController {    PassportService passportService;    @RequestMapping("login")    public Object doLogin(HttpSession session, String username, String password) {        User user = passportService.doLogin(username, password);        session.setAttribute("user", user);        return WebResult.buildResult().redirectUrl("/student/index");    }}

        总结

        通过@ControllerAdvice和@ExceptionHandler,可以实现全局统一管理Controller层之外的异常,这大大简化了代码,提高了扩展性和可维护性。技术人员可以根据需要自定义处理逻辑和返回信息,而无需在每个Controller方法中繁琐地处理异常。

    转载地址:http://aseyk.baihongyu.com/

    你可能感兴趣的文章
    MySQL 快速创建千万级测试数据
    查看>>
    mysql 快速自增假数据, 新增假数据,mysql自增假数据
    查看>>
    MySql 手动执行主从备份
    查看>>
    Mysql 批量修改四种方式效率对比(一)
    查看>>
    mysql 批量插入
    查看>>
    Mysql 报错 Field 'id' doesn't have a default value
    查看>>
    MySQL 报错:Duplicate entry 'xxx' for key 'UNIQ_XXXX'
    查看>>
    Mysql 拼接多个字段作为查询条件查询方法
    查看>>
    mysql 排序id_mysql如何按特定id排序
    查看>>
    Mysql 提示:Communication link failure
    查看>>
    mysql 插入是否成功_PDO mysql:如何知道插入是否成功
    查看>>
    Mysql 数据库InnoDB存储引擎中主要组件的刷新清理条件:脏页、RedoLog重做日志、Insert Buffer或ChangeBuffer、Undo Log
    查看>>
    mysql 数据库中 count(*),count(1),count(列名)区别和效率问题
    查看>>
    mysql 数据库备份及ibdata1的瘦身
    查看>>
    MySQL 数据库备份种类以及常用备份工具汇总
    查看>>
    mysql 数据库存储引擎怎么选择?快来看看性能测试吧
    查看>>
    MySQL 数据库操作指南:学习如何使用 Python 进行增删改查操作
    查看>>
    MySQL 数据库的高可用性分析
    查看>>
    MySQL 数据库设计总结
    查看>>
    Mysql 数据库重置ID排序
    查看>>