错误处理显身手

228 阅读3分钟

前言

某位大佬说过: 当我们开发一个接口的时候有 60 % ~ 70 % 的代码都是在处理错误。

异常

在 Java 中,异常是在程序运行期间发生的不正常情况

异常分类

Java 的异常主要分为两类: 检查性异常 (检查性异常要求我们在编译时必须处理该异常)、非检查性异常 ()非受检异常在运行时可能抛出,无需在编译时强制处理。

  • 检查性异常 (Checked Exception): SQLException、IOException、FileNotFoundException、ClassNotFoundException
  • 非检查性异常 (Unchecked Exception): IndexOutOfBoundsException、NullPointerException、NumberFormatException

image.png

在 Java 中所有的异常都继承与异常基类 Exception 而 Exception 类又继承与顶级错误处理类 Throwable 通过上面的继承关系图我们可以看出无论是 Error 错误还是 Exception 异常都继承与 Throwable。

提示: 当我们需要在项目模块中自定义异常时通常会选择非检查性异常!

public class CommonException extends RuntimeException {

}

异常处理

在 Java 语言中我们处理异常一般会采取两种方式:一种是向上抛异常交给他的调用方进行处理,另一种是通过 try {}catch(Exception ex) {} 来处理异常。

  • 第一种
public void testThrowsSolveException() throws IOException{
   log.info("测试 throws 异常处理")
}
  • 第二种
public void testTryCatchSolveException() {

    try {
        // 业务方法
    } catch(IOException ex) { 
    }
}

自定义异常

自定义检查性异常

// 自定义检测性异常
public class CustomException extends Exception {

    private static final long serialVersionUID = 8312907182931783379L;
    
    private ErrorInfo errorInfo;
    
    private String errorMsg;

    private String errorCode;

     public CustomException() {
        super();
    }
      public CustomException() {
        super();
    }


    public CustomException(ErrorInfo errorInfo, String errorMsg) {
        this.errorMsg = Optional.ofNullable(errorMsg).orElse(errorInfo::getErrorMssg);
        this.errorCode = Optional.ofNullable(errorInfo::getErrorCode).orElse(null);
    }
}

自定义非检查性异常

public class SystemException extends RuntimeException {

     private static final long serialVersionUID = 8312907182931723379L;
    
    private ErrorInfo errorInfo;
    
    private String errorMsg;

    private String errorCode;

    Project + Moudle = 
    

    public SystemException() {
        super();
    }

    
    public SystemException(String errorMsg) {
        super(errorMsg);
    }
    
    public CustomException(ErrorInfo errorInfo, String errorMsg) {
        this.errorMsg = Optional.ofNullable(errorInfo::getErrorMsg).orElse(errorMsg);
        this.errorCode = Optional.ofNullable(errorInfo::getErrorCode).orElse(null);
    }
}

异常捕获规范

// 如果是 RPC 调用你的 @Transactional(rollback = Throwable.class)

try{
    ……
} catch (Throwable e){ 
    // log.error() ???? 
    throw new BIZException(e); 
} catch (Biz1Exception e){ 
    throw new BIZException(e.getMessage()); 
} catch (Biz2Exception e){
    throw new Exception(e); 
} catch (Biz3Exception e){
    throw new Exception(……); 
} catch (Biz4Exception e){
    …… 
} catch (Exception e){
    throw e;
}

  • 不要重复包装同样类型的异常参数。
  • 需要抛出对应的异常栈信息。
  • 不能使用低抽象级别的异常去包装高抽象级别的异常,这样容易丢失自定义信息 如: 异常码、异常信息。
  • 异常转移错误,将业务异常直接转义成系统异常。
  • 不抛出异常也不记录 Log , 直接吃掉异常。

统一业务异常处理模板

Sl4j
public abstract class ServiceTemplate<REQUEST,RESPONSE> {

    // 业务验证 比如说是否成年 金额是否足够 ? 商品是否还有库存
    public abstract boolean isSatisfiedBy(REQUEST requestParam);

    // 业务模板
    protected RESPONSE process(REQUEST requestParam) {

        // 业务校验 
        try {
             // 业务校验 
             isSatisfiedBy(requestParam);
             return doProcess(requestParam);
        } catch(ServiceException ex) {
            log.warm("发生业务异常: {}", ex.getErrorMsg(), ex);
            return Results.fail(ex.getErrorCode(),ex.getErrorMsg());
        } catch(SystemException ex) {
             log.error("发生系统异常: {}", ex.getErrorMsg(), ex);
            return Results.fail(ex.getErrorCode(),ex.getErrorMsg());
        } catch(RpcException ex) {
            log.error("发生 Rpc 业务异常: {}", ex.getErrorMsg(), ex);
            return Results.fail(ex.getErrorCode(),ex.getErrorMsg());
        } catch(Throwable ex) {
            log.error("发生未知异常: {}", ex.getErrorMsg(), ex);
        }
    }

    // 业务执行
    public RESPONSE abstract doProcess(REQUEST requestParam);
}

new ServiceTemplate<Integer,Long>(() -> {

     // 业务验证 比如说是否成年 金额是否足够 ? 商品是否还有库存
    public boolean isSatisfiedBy(Integer requestParam) {
        
    }
    protected Long doProcess( Integer requestParam) {
        
    }
}).process();

Spring Boot 全局异常处理器

@Slf4j
@Aspect
@Component
@RestControllAdiver
public class GlobalExceptionAop {
    /**
     * execution(* com.ayi.service ..*.*(..)):表示 rpc 接口实现类包中的所有方法
     */
    @Pointcut("execution(* com.ayi.service ..*.*(..))")
    public void pointcut() {}

    @Around(value = "pointcut()")
    public Object handleException(ProceedingJoinPoint joinPoint) {
        try {
            //如果对传入对参数有修改,那么需要调用joinPoint.proceed(Object[] args)
            //这里没有修改参数,则调用joinPoint.proceed()方法即可
            return joinPoint.proceed();
        } catch (ServiceException ex) {
            // 对于业务异常,应该记录 warn 日志即可,避免无效告警
            log.warn("全局捕获业务异常", ex);
            return Results.fail(ex.getCode(), ex.getMessage());
        } catch (RpcException ex) {
            log.error("全局捕获第三方rpc调用异常", ex);
            return Results.fail(ex.getCode(), ex.getMessage());
        } catch (SystemException ex) {
            log.error("全局捕获系统异常", ex);
            return Results.fail(ex.getCode(), ex.getMessage());
        } catch (Throwable ex) {
            log.error("全局捕获未知异常", ex);
            return Results.fail(ex.getMessage());
        }
    }
}