
本文共 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。
- 第二种思路:使用接口的默认方法
- 第三种思路:使用加强Controller做全局异常处理
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("系统错误"); } }}
@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方法中繁琐地处理异常。
发表评论
最新留言
关于作者
