博客
关于我
@ControllerAdvice+@ExceptionHandler全局处理Controller层异常 及其 原理
阅读量:786 次
发布时间: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/

    你可能感兴趣的文章
    15种下载文件的方法&文件下载方法汇总&超大文件下载
    查看>>
    anaconda、python卸载后重装以及anaconda--443
    查看>>
    AWVS工具太顶了,漏洞扫描工具AWVS介绍及安装教程
    查看>>
    CentOS 系列:CentOS 7 使用 virt-install + vnc 图形界面/非图形界面 创建虚拟机
    查看>>
    CentOS 系列:CentOS 7文件系统的组成
    查看>>
    CentOS系列:【Linux】CentOS7操作系统安装nginx实战(多种方法,超详细)
    查看>>
    CSDN----Markdown编辑器
    查看>>
    Docker容器进入的4种方式(推荐最后一种)
    查看>>
    Docker部署postgresql-11以及主从配置
    查看>>
    EnvironmentNotWritableError: The current user does not have write permissions to the target environm
    查看>>
    Golang起步篇(Windows、Linux、mac三种系统安装配置go环境以及IDE推荐以及入门语法详细释义)
    查看>>
    Hyper-V系列:windows11开启系统自带安卓虚拟机并安装apk包
    查看>>
    Hyper-V系列:微软官方文章
    查看>>
    idea打war包的两种方式
    查看>>
    Java系列:【注释模板】IDEA中JAVA类、方法注释模板教程
    查看>>
    JS系列(仅供参考):【浏览器编程】浏览器F12调试工具面板详解和JavaScript添加断点
    查看>>
    Kali 更换源(超详细,附国内优质镜像源地址)
    查看>>
    kali安装docker(亲测有效)
    查看>>
    Linux系列:Linux目录分析:[/] + [/usr] + [/usr/local] + [/usr/local/app-name]、Linux最全环境配置 + 动态库/静态库配置
    查看>>
    Linux系列:ubuntu各版本之间的区别以及Ubuntu、kubuntu、xUbuntu、lubuntu等版本区别及界面样式
    查看>>