基于SpringMVC实现全局异常处理
1. 首先要了解怎么自定义异常类,因为开发中我们会遇到各种不同的需求,按照需求的不同就需要很多不同类型的异常
要创建自己的异常类,需要继承RuntimeException类作为它的子类。我们现在会创建一个类继承它
但这个类不是具体的自定义异常类,而是作为中间类存在,方便后面统一处理(后面会详细说明)。
public class BaseError extends RuntimeException{
//MyErrorInfo 这个类是我自定义的一个包含异常的各种状态属性的实体类。
private MyErrorInfo error = new MyErrorInfo();
//有参构造写起来,其中code:状态码(自定义);message:异常描述;
// status:http响应状态码 404等;e:具体的异常类 我们一般不用这个参数
public BaseError(int code, String message, HttpStatus status, Throwable e) {
super(message, e);
error.setStatus(status.value());
error.setError(message);
error.setCode(code);
}
//这个构造函数是我们最常用的,不解释了
public BaseError(int code, String message, HttpStatus status) {
this(code, message, status, null);
}
//下面的两个方法就是普通的get/set.
public MyErrorInfo getError() {
return error;
}
public void setError(MyErrorInfo error) {
this.error = error;
}
}
至此我们的自定义异常类的父类创建完成,他继承了RuntimeException并且定义了异常传参规则
2. 接下来我们开始定义具体的异常类,这里就定义一种NotFoundError,顾名思义就是代码里加入查询语句本来有返回值但因为其他原因返回是null,返回是null的话可能会影响我们代码往下运行,这个时候我们就会抛出这个异常
public class NotFoundError extends BaseError {
//无参构造函数,第一个参数是自定义状态码,第二个是异常描述(这里是无参构造,我们给了默认值)
//第三个是HTTP返回是的状态码,这里我们填入404
public NotFoundError() {
super(40400, "请求资源不存在", HttpStatus.NOT_FOUND);
}
//有参构造,手动传进来一个异常描述信息。
public NotFoundError(String message) {
super(40400, message, HttpStatus.NOT_FOUND);
}
//有参构造
public NotFoundError(int code, String message) {
super(code, message, HttpStatus.NOT_FOUND);
}
//静态方法,抛异常时可以直接调用这个方法,将业务逻辑里面的值传进来,这样前端展示
//异常的时候我们可以更容易定位到程序的错误位置
public static NotFoundError byId(String id) {
return new NotFoundError(40401, String.format("请求资源 %s 不存在", id));
}
public static NotFoundError byUpdate(String tableName) {
return new NotFoundError(40402, String.format("更新 `%s` 失败, 可能记录已经被其他人修改", tableName));
}
}
以上就是我们定义的NotFoundError ,所有的方法包括静态方法最后都会通过Super()调用到父类BaseError,再由父类调用Super()重写了RuntimeException中的异常信息
3.全局异常处理类
@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionController {
@ExceptionHandler(BaseError.class)
@ResponseBody
public Object baseError(HttpServletRequest request, BaseError e){
MyErrorInfo myErrorInfo = e.getError();
myErrorInfo.setPath(request.getServletPath());
if (e.getCause() != null)
myErrorInfo.setMessage(e.getCause().getMessage());
log.info("baseError", e); // 明确的错误,INFO级别
return ResponseEntity.status(myErrorInfo.getStatus()).body(myErrorInfo);
}
@ExceptionHandler(value = MethodArgumentNotValidException.class)
@ResponseBody
public Object validError(HttpServletRequest request, MethodArgumentNotValidException e){
BindingResult result = e.getBindingResult();
for (ObjectError error : result.getAllErrors()) {
log.info("请求参数校验错误: {}", error.getDefaultMessage()); // 明确错误,INFO级别
}
List errors = result.getAllErrors().stream().map(error -> error.getDefaultMessage()).collect(Collectors.toList());
MyErrorInfo myErrorInfo = new MyErrorInfo();
myErrorInfo.setStatus(HttpStatus.BAD_REQUEST.value());
myErrorInfo.setCode(40009);
myErrorInfo.setError("请求参数校验错误");
myErrorInfo.setPath(request.getServletPath());
myErrorInfo.setMessage(errors);
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(myErrorInfo);
}
@ExceptionHandler(value = MissingServletRequestParameterException.class)
@ResponseBody
public Object missingParamError(HttpServletRequest request, MissingServletRequestParameterException e){
MyErrorInfo myErrorInfo = new MyErrorInfo();
myErrorInfo.setStatus(HttpStatus.BAD_REQUEST.value());
myErrorInfo.setCode(40010);
myErrorInfo.setError("参数缺失");
myErrorInfo.setPath(request.getServletPath());
myErrorInfo.setMessage(e.getMessage());
log.info("参数缺失", e); // 明确的错误,INFO级别
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(myErrorInfo);
}
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Object sysError(HttpServletRequest request, Exception e){
MyErrorInfo myErrorInfo = new MyErrorInfo();
myErrorInfo.setStatus(500);
myErrorInfo.setException(e.getClass().getSimpleName()); // 异常类:全称还是简称
myErrorInfo.setPath(request.getServletPath());
myErrorInfo.setMessage(e.getMessage());
log.warn("系统错误", e); // 不明确错误,WARN级别
return ResponseEntity.status(500).body(myErrorInfo);
}
}
-
@ControllerAdvice:表明此类是全局异常处理类,所有的异常抛出后都会进来
-
@ExceptionHandler():表明此方法是用来出来哪一类异常的,里面的参数可以写我们自定义的异常类比如NotFoundError.class。但因为我们所有的自定义异常类都有一个父类
BaseError.class,所以只要是自定义异常,不管是什么类型的异常,我们都可以在@ExceptionHandler()的参数中写BaseError.class。
这样做的好处就在于编码简单,不需要每个自定义异常都去写一个方法来处理。 -
@ResponseBody:表示数据返回前端以JSON形式返回
上图的代码中,除开第一个方法是处理自定义异常的,其他的方法都是处理java中自带异常的方法,而其中最后一个方法sysError()可以看到他是处理所有异常的方法,他的@ExceptionHandler()注解中的值是Exception,所有异常的父类,也就是说假如现在有一个异常,我们全局异常处理类前面的所有方法都没有捕捉到,那他最终还是会被最后一个方法捕捉并返回前端
4.调用
// 检查ID是否存在
OcAssureAmt oldEntity = ocAssureAmtService.getById(id);
if (oldEntity == null)
throw NotFoundError.byId(id);