文章目录
0、前面文章说过最近公司搭建新系统,这次我们聊聊关于全局异常处理。
1、异常体系和分类
1.1、先说说异常体系
所有的异常都是继承Throwable而来,其中Throwable分为Error和Exception。
Error是错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时, 如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
Exception,是另外一个非常重要的异常子类。它规定的异常是程序本身可以处理的异常。Exception类分解成两个分支,一支派生于RuntimeException,另一支派生于其他异常。异常和错误的区别是,异常是可以被处理的,而错误是没法处理的。
1.2、异常分类
异常分类有可检查异常、不可检查异常 ;
可检查异常就是编译器要求你必须处置的异常。不知道你编程的时候有没有遇到过,你写的某段代码,编译器要求你必须要对这段代码try…catch,或者throws exception, 如果你遇见过,没错,这就是检查异常,也就是说,你代码还没运行呢,编译器就会检查你的代码,会不会出现异常,要求你对可能出现的异常必须做出相应的处理。
对检查异常(checked exception)的几种处理方式:
- 1、继续抛出,消极的方法,一直可以抛到java虚拟机来处理,就是通过throws exception抛出。注意,对于检查的异常必须处理,或者必须捕获或者必须抛出
如何区分检查异常:除了RuntimeException与其子类,以及错误(Error),其他的都是检查异常(绝对的大家族)。
不可检查异常就是编译器不要求强制处置的异常,虽然你有可能出现错误,但是我不会在编译的时候检查,没必要,也不可能。为什么呢?你想想非检查异常都有哪些?NullPointerException,IndexOutOfBoundsException,VirtualMachineError等,这些异常你编译的时候检查吗?那我还要不要运行了,等死人啊。再说了,明明可以运行时检查,都在编译的时候检查,你写的代码还能看吗?而且有些异常只能在运行时才能检查出来,比如空指针,堆溢出等。
对未检查的异常(unchecked exception )的几种处理方式:
- 1、继续抛出;
- 2、不处理。 一般我们是不处理的,因为你很难判断会出什么问题,而且有些异常你也无法运行时处理,
比如空指针,需要人手动的去查找。而且,捕捉异常并处理的代价远远大于直接抛出。
2、常见的异常
- 输入输出异常:IOException
- 算术异常类:ArithmeticExecption
- 空指针异常类:NullPointerException
- 类型强制转换异常:ClassCastException
- 操作数据库异常:SQLException
- 文件未找到异常:FileNotFoundException
- 数组负下标异常:NegativeArrayException
- 数组下标越界异常:ArrayIndexOutOfBoundsException
- 违背安全原则异常:SecturityException
- 文件已结束异常:EOFException
- 字符串转换为数字异常:NumberFormatException
- 方法未找到异常:NoSuchMethodException
3、现在说说统一处理异常的必要性:
- 避免try…catch过多,使得代码可维护性、可读性降低
- 统一出现异常后返回给前端的数据格式
- Validator参数校验器的时候,参数校验不通过会抛出异常,无法用try…catch捕获,只能使用全局异常处理器。
4、统一处理异常解决方案:
- 统一异常拦截(GlobalExceptionHandler)、
- 统一定义异常code枚举类(ExceptionCodeEnum)、
- 统一定义业务异常通用类(BusinessException)。
@Slf4j
@Component
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.error("REST API exception handler got a message not readable exception, message:{},[request:{}]}", ExceptionUtils.getStackTrace(ex), request);
log.info("参数不能读异常:{}\nat:{}", ExceptionUtils.getStackTrace(ex));
return new ResponseEntity<>(Result.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), ex.getMessage(), null, null), OK);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.error("REST API exception handler got a message not writable exception:\n" + ExceptionUtils.getStackTrace(ex));
return new ResponseEntity<>(Result.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), ex.getMessage(), null, null), OK);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String message = ex.getFieldError().getDefaultMessage();
String method = ex.getParameter().getMethod().getName();
String field = ex.getFieldError().getField();
log.error("except-error-monitor, @Valid方法参数校验失败,method: {}, field: {}, detail: {}", method, field, ex.getMessage());
log.info("参数异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n "));
return new ResponseEntity<>(Result.of(HttpStatus.UNPROCESSABLE_ENTITY.value(), "参数校验失败:" + message, null, null), OK);
}
@ExceptionHandler({IllegalArgumentException.class})
protected ResponseEntity<Object> handlerArgumentException(Exception ex, WebRequest request) {
// 异常的简略信息输出至告警群
log.error("except-error-monitor, 参数异常:{}", ex.getMessage());
log.info("参数异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n "));
return new ResponseEntity<>(Result.of(PARAM_ERROR.getCode(), "参数校验失败:" + ex.getMessage(), null, null), OK);
}
@ExceptionHandler(BusinessException.class)
protected ResponseEntity<Object> handleBusinessException(BusinessException ex, WebRequest request) {
// 异常的简略信息输出至告警群
if (!ex.getErrCode().equals(BIZ_ERROR_NOT_ALERT.getCode())) {
log.error("except-error-monitor, 业务异常:{}", ex.getMessage());
}
log.info("业务异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n "));
return new ResponseEntity<>(Result.of(Optional.ofNullable(ex.getErrCode()).orElse(BIZ_ERROR.getCode()), ex.getMessage(), null, null), OK);
}/*
*/
@ExceptionHandler(ClientHandlerException.class)
protected ResponseEntity<Object> handleClientHandlerException(BusinessException ex, WebRequest request) {
log.info("网络异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n "));
return new ResponseEntity<>(Result.of(NETWORK_ERROR.getCode(), NETWORK_ERROR.getDesc(), null, null), OK);
}
@ExceptionHandler(RedissonLockException.class)
protected ResponseEntity<Object> handleRedissonLockException(RedissonLockException ex, WebRequest request) {
log.error("except-error-monitor, 分布式锁异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n "));
return new ResponseEntity<>(Result.of(REPEAT_EXECUTE), OK);
}
@ExceptionHandler(Throwable.class)
protected ResponseEntity<Object> handleGeneralException(Throwable ex, WebRequest request) {
log.error("未知异常:{}\nat:{}", ex.getMessage(), Arrays.toString(ex.getStackTrace()).replaceAll(",", "\n "));
return new ResponseEntity<>(Result.of(SERVICE_ERROR), OK);
}
/**
* 授权异常统一处理
* @Author YangYaNan
* @Date Created in 18:30 2022/6/17
* @Description
* @param ex, request
* @return org.springframework.http.ResponseEntity<java.lang.Object>
**/
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> accessDeniedException(AccessDeniedException ex, WebRequest request){
log.error("except-error-monitor, 用户授权异常:{}, [request:{}]", ExceptionUtils.getStackTrace(ex), request);
return new ResponseEntity<>(Result.of(FORBIDDEN_OPERATION), FORBIDDEN);
}
/**
* 认证异常统一处理
* @Author YangYaNan
* @Date Created in 18:31 2022/6/17
* @Description
* @param ex, request
* @return org.springframework.http.ResponseEntity<java.lang.Object>
**/
@ExceptionHandler(AuthenticationException.class)
public ResponseEntity<Object> authenticationException(AuthenticationException ex, WebRequest request){
log.error("except-error-monitor, 用户认证异常:{}, [request:{}]", ExceptionUtils.getStackTrace(ex), request);
return new ResponseEntity<>(LoginFailureHandler.onAuthenticationFailure(ex), OK);
}
}
public enum ExceptionCodeEnum {
SUCCESS(200, "请求成功"),
ACCESS_ERROR(503, "当前访问流量过大,请稍后重试"),
// 2xxx 业务异常
BIZ_ERROR(2000, "业务操作失败"),
ILLEGAL_OPERATION_ERROR(2001, "非正常操作提示"),
// 不告警
BIZ_ERROR_NOT_ALERT(2999, "业务操作失败"),
FILE_FORMAT_ERROR(2008, "文件格式错误"),
FURTHER_EXECUTE_SERVICE(2009, "需要补充资料"),
// 4xxx 客户端异常
BAD_REQUEST(4001, "非法请求"),
FORBIDDEN_OPERATION(4003, "无权限操作"),
TIMEOUT(4004, "请求超时"),
UNAUTHORIZED_ACCESS(401, "未找到当前登录用户信息"),
ERROR_TOKEN(4006,"无效的TOKEN"),
NETWORK_ERROR(4007,"网络异常"),
// 5xxx 第三方服务异常
RPC_ERROR(5000, "调用远程接口失败"),
// 6xxx 基础中间件异常
DB_ERROR(6000, "数据库操作失败"),
// 7xxx 数据问题
PARAM_ERROR(7000, "参数错误"),
EXIST(7004, "请求数据已存在");
/**
* 获取code
*
* @return int code
*/
public int getCode() {
return code;
}
/**
* 获取desc
*
* @return string desc
*/
public String getDesc() {
return desc;
}
/**
* @param code 代码
* @param desc 详情
*/
ExceptionCodeEnum(final int code, final String desc) {
this.code = code;
this.desc = desc;
}
private int code;
private String desc;
}
public class BusinessException extends RuntimeException {
private Integer errCode;
private String errMsg;
private Integer httpStatusCode;
public BusinessException(String errMsg) {
super(errMsg);
this.errMsg = errMsg;
}
public BusinessException(String errMsg, Throwable cause) {
super(errMsg, cause);
this.errMsg = errMsg;
}
public BusinessException(Integer errCode, String errMsg) {
this(errMsg);
this.errCode = errCode;
}
public BusinessException(ExceptionCodeEnum code) {
this(code.getCode(), code.getDesc());
}
public BusinessException(ExceptionCodeEnum code, String errMsg) {
this(code.getCode(), errMsg);
}
public BusinessException(Integer errCode, String errMsg, Throwable cause) {
this(errMsg, cause);
this.errCode = errCode;
}
public BusinessException(Integer errCode, String errMsg, Integer httpStatusCode) {
this(errCode, errMsg);
this.httpStatusCode = httpStatusCode;
}
public BusinessException(Integer errCode, String errMsg, Integer httpStatusCode, Throwable cause) {
this(errCode, errMsg, cause);
this.httpStatusCode = httpStatusCode;
}
public Integer getErrCode() {
return this.errCode;
}
public String getErrMsg() {
return this.errMsg;
}
public Integer getHttpStatusCode() {
return this.httpStatusCode;
}
}