1.当没有异常处理的时候
一般如果系统发生异常,接口就状态码就会返回500,返回回来一个json数据。
响应回来的数据是一个JSON格式的数据。但这种JSON格式的数据还是我们开发规范当中所提到的统一响应结果Result吗?显然并不是。由于返回的数据不符合开发规范,所以前端并不能解析出响应的JSON数据。
当我们没有做任何的异常处理时,我们三层架构处理异常的方案:
Mapper接口在操作数据库的时候出错了,此时异常会往上抛(谁调用Mapper就抛给谁),会抛给service。
service 中也存在异常了,会抛给controller。
而在controller当中,我们也没有做任何的异常处理,所以最终异常会再往上抛。最终抛给框架之后,框架就会返回一个JSON格式的数据,里面封装的就是错误的信息,但是框架返回的JSON格式的数据并不符合我们的开发规范。
2.解决方案
那么在三层构架项目中,出现了异常,该如何处理?
方案一:在所有Controller的所有方法中进行try…catch处理
缺点:代码臃肿(不推荐)
方案二:全局异常处理器
好处:简单、优雅(推荐)
3.全局异常处理器
我们该怎么样定义全局异常处理器?
定义全局异常处理器非常简单,就是定义一个类,在类上加上一个注解@RestControllerAdvice,加上这个注解就代表我们定义了一个全局异常处理器。
在全局异常处理器当中,需要定义一个方法来捕获异常,在这个方法上需要加上注解@ExceptionHandler。通过@ExceptionHandler注解当中的value属性来指定我们要捕获的是哪一类型的异常。
@RestControllerAdvice
public class GlobalExceptionHandler {
//处理异常
@ExceptionHandler(Exception.class) //指定能够处理的异常类型
public Result ex(Exception e){
e.printStackTrace();//打印堆栈中的异常信息
//捕获到异常之后,响应一个标准的Result
return Result.error(500,"对不起,操作失败,请联系管理员");
}
}
@RestControllerAdvice = @ControllerAdvice + @ResponseBody
处理异常的方法返回值会转换为json后再响应给前端。
4.异常处理器拓展
对400和405等客户端错误产生的异常进行补抓,并把返回码统一管理。
//全局异常处理器
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 请求参数异常/缺少--400
*
* @param e
* @return
*/
@ExceptionHandler({
MissingServletRequestParameterException.class,
MethodArgumentTypeMismatchException.class,
RequestRejectedException.class,
BindException.class}
)
public Result missingServletRequestParameterException(Exception e) {
return Result.error(StatusCode.PARAMETERERROR, "缺少参数或参数错误");
}
/**
* 请求方法错误--405
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result httpRequestMethodNotSupportedExceptionHandler(Exception e){
log.error("请求方法错误");
return Result.error(StatusCode.METHODERROR,"请求方法错误");
}
/**
* 其他异常--500
*/
@ExceptionHandler(Exception.class)
public Result exceptionHandler(Exception e) {
e.printStackTrace();
return Result.error(StatusCode.SERVERERROR,"对不起,操作失败,请联系管理员");
}
}
状态码统一管理:
public class StatusCode {
/**
* 操作成功
*/
public static final int OK = 200;
/**
* 请求参数异常或缺失
*/
public static final int PARAMETERERROR = 400;
/**
* 资源不存在
*/
public static final int NOTFOUND = 404;
/**
* 请求方法错误
*/
public static final int METHODERROR = 405;
/**
* 服务端错误
*/
public static final int SERVERERROR = 500;
}
5.异常处理器与自定义异常结合
有一种设计是我们Result.error之类返回错误相应对的都会集中到全局异常处理器就行处理。
先继承至运行异常书写一个通用的异常类,多一个code,全局异常处理器处理时候便于返回响应的错误代码。
@Getter
public class CommonException extends RuntimeException{
private int code;
public CommonException(String message, int code) {
super(message);
this.code = code;
}
public CommonException(String message, Throwable cause, int code) {
super(message, cause);
this.code = code;
}
public CommonException(Throwable cause, int code) {
super(cause);
this.code = code;
}
}
然后我们就可以让其他类继承这个异常类生成新的异常类。
例:
public class DbException extends CommonException{
public DbException(String message) {
super(message, 500);
}
public DbException(String message, Throwable cause) {
super(message, cause, 500);
}
public DbException(Throwable cause) {
super(cause, 500);
}
}
然后在需要抛出异常的地方抛出异常,然后在全局异常处理器中对我们定义的不同异常进行补抓处理:
@RestControllerAdvice
@Slf4j
public class CommonExceptionAdvice {
@ExceptionHandler(DbException.class)
public Object handleDbException(DbException e) {
log.error("mysql数据库操作异常 -> ", e);
return processResponse(e);
}
@ExceptionHandler(CommonException.class)
public Object handleBadRequestException(CommonException e) {
log.error("自定义异常 -> {} , 异常原因:{} ",e.getClass().getName(), e.getMessage());
log.debug("", e);
return processResponse(e);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getAllErrors()
.stream().map(ObjectError::getDefaultMessage)
.collect(Collectors.joining("|"));
log.error("请求参数校验异常 -> {}", msg);
log.debug("", e);
return processResponse(new BadRequestException(msg));
}
@ExceptionHandler(BindException.class)
public Object handleBindException(BindException e) {
log.error("请求参数绑定异常 ->BindException, {}", e.getMessage());
log.debug("", e);
return processResponse(new BadRequestException("请求参数格式错误"));
}
@ExceptionHandler(NestedServletException.class)
public Object handleNestedServletException(NestedServletException e) {
log.error("参数异常 -> NestedServletException,{}", e.getMessage());
log.debug("", e);
return processResponse(new BadRequestException("请求参数处理异常"));
}
@ExceptionHandler(Exception.class)
public Object handleRuntimeException(Exception e) {
log.error("其他异常 uri : {} -> ", WebUtils.getRequest().getRequestURI(), e);
return processResponse(new CommonException("服务器内部异常", 500));
}
private ResponseEntity<R<Void>> processResponse(CommonException e){
return ResponseEntity.status(e.getCode()).body(R.error(e));
}
}