统一异常处理
代码托管地址:https://gitee.com/ZomiCC/code/tree/master/exception
非统一异常处理
A代码:try...catch...
B代码:try...catch...
C代码:try...catch...
D代码:try...catch...
统一异常处理
此文应用到了[统一校验]部分功能,没有看多可先参考上文:统一校验
统一处理,我们很容易会想到spring中的AOP。没错,本篇文章核心就是使用spring为我们提供的两个注解@RestControllerAdvice + @ExceptionHandler 来实现异常的统一处理。
我们的任务主要分以下几步:
- 自定义统一异常类BizException,规范异常类型,便于ExceptionHandler使用
- 对controller切面,实现对BindingResult(即调用者请求参数)的统一异常/校验处理,我们称这一步为前置异常捕获
- 对service切面,捕获所有的service异常,统一封装为BizException,再返回给controller,方便RestControllerAdvice+ExceptionHandler处理异常类型。我们称这一步为业务异常捕获
- 在全局异常处理器中,也就是RestControllerAdvice+ExceptionHandler的处理方法中,封装响应结果返回给调用者。
关键代码
依赖包
<!-- valid校验 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- 想要BindResult生效,这个依赖一定要有 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
统一异常定义
package com.example.exception.exception;
public class BizException extends RuntimeException{
private String code;
private String msg;
public BizException(String code, String msg){
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
前置异常捕获
/**
* 前置异常切面
*/
@Slf4j
@Aspect
@Component
public class ControllerAspect {
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
"&&args(..,org.springframework.validation.BindingResult)")
public void controllerAspect() {
}
@Around("controllerAspect()")
public Object doBefore(ProceedingJoinPoint joinPoint) throws Throwable{
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof BindingResult) {
BindingResult result = (BindingResult) arg;
if (result.hasErrors()) {
log.error("请求参数校验错误");
FieldError fieldError = result.getFieldError();
if (fieldError != null) {
//return ResponseUtil.fail(new BizException("111111", fieldError.getDefaultMessage()));
throw new BizException("111111", fieldError.getDefaultMessage());
}
//return ResponseUtil.fail(new BizException("999999", "未知参数错误"));
throw new BizException("222222", "未知参数错误┭┮﹏┭┮");
}
}
}
return joinPoint.proceed();
}
}
业务异常捕获
/**
* Service异常捕获,将Service中的所有异常,转换为统一的BizException,方便ExceptionHandler统一处理
*/
@Aspect
public class ServiceAspect {
@Pointcut("execution(public * com.example.exception.service.*.*(..))")
public void serviceAspect() {
}
@Around("serviceAspect()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (Exception e) {
if (e instanceof BizException) {
throw e;
} else {
throw new BizException("999999", "系统错误^_^");
}
}
}
}
全局异常处理器
package com.example.exception.handler;
import com.example.exception.dto.ResponseEntity;
import com.example.exception.exception.BizException;
import com.example.exception.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 业务异常处理
*/
@ExceptionHandler(BizException.class)
public ResponseEntity bizExceptionHandler(BizException e) {
return ResponseUtil.fail(e);
}
/**
* 通用异常处理
*/
@ExceptionHandler(Exception.class)
public ResponseEntity exceptionHandler(Exception e) {
log.error(e.getMessage(), e);
return ResponseUtil.fail("999999", "系统错误(#^.^#)");
}
}
流程图参考
There are two things to do in a day: a happy thing and a difficult one.