对于与数据库相关的 Spring MVC 项目,我们通常会把 事务 配置在 Service层,当数据库操作失败时让 Service 层抛出运行时异常,Spring 事物管理器就会进行回滚。
如此一来,我们的 Controller 层就不得不进行 try-catch Service 层的异常,否则会返回一些不友好的错误信息到客户端。
但是,Controller 层每个方法体都写一些模板化的 try-catch 的代码,很难看也难维护,特别是还需要对 Service 层的不同异常进行不同处理的时候。
/** * 手动处理 Service 层异常和数据校验异常的示例 * @param dog * @param errors * @return */ @PostMapping(value = "") 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 层异常处理,只要设计得当,就再也不用在 Controller 层进行 try-catch 了!
而且,@Validated 校验器注解的异常,也可以一起处理,无需手动判断绑定校验结果 BindingResult/Errors 了!
1.@ControllerAdvice 注解定义全局异常处理类
//统一在这个类中处理异常,全局拦截指定的异常
@ControllerAdvice public class ExceptionHandle {
2.@ExceptionHandler 注解声明异常处理方法
//统一在这个类中处理异常,全局拦截指定的异常
@ControllerAdvice public class ExceptionHandle { private static final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); @ExceptionHandler(value = Exception.class) //申明捕获那个异常类
@ResponseBody //返回给浏览器的是一个json格式,上面又没有@RestController,所以在此申明@ResponseBody
public Result handle(Exception e) { return “错误!”; } }
方法 handleException() 就会处理所有 Controller 层抛出的 Exception 及其子类的异常。
实施统一异常管理的案例:
1.将返回结果信息进行封装,新建Result类
public class Result<T> { /** * 错误码 */
private Integer code; /** * 提示信息 */
private String msg; /** * 具体内容 */
private T data; public Result() { } public Result(Integer code, String msg) { this(code, msg, null); } public Result(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } //。。。 }
2.为方便对结果信息进行处理,新建结果信息工具类:ResultUtils
public class ResultUtil { public static Result success(Object object) { return new Result(0, "成功", object); } public static Result success() { return success(null); } public static Result error(Integer code, String msg) { return new Result(code, msg); } }
3.为了方便对异常信息进行处理,自定义异常信息类:GirlException
public class GirlException extends RuntimeException { //这个地方不要写exception,因为Spring是只对运行时异常进行事务回滚, //如果抛出的是exception是不会进行事务回滚的。
private Integer code; public GirlException(ResultEnum resultEnum) { this(resultEnum.getMsg(), resultEnum.getCode()); } public GirlException(String message, Integer code) { super(message); this.code = code; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
4.为方便统一管理异常代码和信息之间的关系,建立枚举类
public enum ResultEnum { UNKOWN_ERROR(-1, "未知错误"), SUCCESS(0, "成功"), PRIMARY_SCHOOL(10, "你可能还在上小学"), MIDDLE_SCHOOL(16, "你可能还在上初中"), ; private Integer code; private String msg; private ResultEnum(Integer code,String msg) { this.msg = msg; this.code = code; } public String getMsg() { return msg; } public Integer getCode() { return code; } }
5.在service层抛出异常
public void getAge(Integer id) throws Exception { Girl girl = girlRepository.getOne(id); Integer age = girl.getAge(); if (age < 10) { throw new GirlException(ResultEnum.PRIMARY_SCHOOL); } else if (age > 10 && age < 16) { throw new GirlException(ResultEnum.MIDDLE_SCHOOL); } }
6.在controller层继续抛异常
//统一异常处理,在controller层中的exception传到exceptionHandler去处理
@GetMapping(value = "girls/getAge/{id}") public void getAge(@PathVariable("id") Integer id) throws Exception { girlService.getAge(id); }
7.在exceptionHandler中处理异常
//统一在这个类中处理异常,全局拦截指定的异常
@ControllerAdvice public class ExceptionHandle { private static final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); @ExceptionHandler(value = Exception.class) //申明捕获那个异常类
@ResponseBody //返回给浏览器的是一个json格式,上面又没有@RestController,所以在此申明@ResponseBody
public Result handle(Exception e) { if (e instanceof GirlException) { GirlException girlException = (GirlException) e; return ResultUtil.error(girlException.getCode(), girlException.getMessage()); } logger.error("【系统异常】", e); return ResultUtil.error(ResultEnum.UNKOWN_ERROR.getCode(), ResultEnum.UNKOWN_ERROR.getMsg()); } }