如何设计java异常

  |   0 评论   |   412 浏览

背景

排查线上问题,只能通过日志,那么如何才能快速定位到问题,这个是需要好好设计的。如果设计得当,可以快速通过日志定位是自己写的代码问题,还是第三方问题,还是别人调用你的接口入参问题。
那么如何来设计异常,才能快速定位到问题呢?

如何设计

出发点:
1、良好的异常信息展示,出现问题可以快速找出原因;
2、被调用时明确告诉调用方是参数不合法还是系统内部出现问题;
3、业务异常友好的提示用户;

实现

异常进行分类

对异常进行分类,一般分为系统异常、业务异常。
1、系统异常包括资源环境异常、第三方异常、代码bug、数据异常、配置异常、参数不合法;
2、业务异常包括操作错误、条件不满足;

捕获异常并抛出

1)入参合法性验证
对外提供的接口都需要验证。
service方法的入参都需要验证。

2)响应结果验证
对第三方的接口返回都需要进行验证,不要等到用的时候才发现数据不正确或者格式不对。

3)业务前置条件验证
如你要买东西,那么需要验证你的余额够不够;你要看视频,需要验证你有没有观看权限等等;这些验证不通过都是需要抛出业务异常的。

验证原则:

1、越早验证越好,越有利于定位问题。
2、过度验证影响性能,需要综合考虑。

统一拦截

确保不会有异常堆栈展示给用户,保证展示给用户的信息是友好的。

web响应

以springmvc为例:
统一异常处理类

@ControllerAdvice
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handleExp(HttpServletRequest request, Exception ex) {
        //记录异常日志
        if (ex instanceof WarnException || ex instanceof MethodArgumentNotValidException) {
            //验证类异常 不报警
            logger.warn("warning occurs:", ex);
        } else {
            logger.error("error occurs:", ex);
        }
        R r = new R();
        if (ex instanceof RRException) {
            r.put("code", ((RRException) ex).getCode());
            r.put("msg", ((RRException) ex).getMsg());
        } else if (ex instanceof DuplicateKeyException) {
            r = R.error("数据库中已存在该记录");
        } else if (ex instanceof AuthorizationException) {
            r = R.error("没有权限,请联系管理员授权");
        } else if (ex instanceof SQLException) {
            r = R.error("数据库异常,请联系管理员");
        } else if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) ex;
            List<ObjectError> errors = validException.getBindingResult().getAllErrors();
            StringBuilder sb = new StringBuilder();
            for (ObjectError error : errors) {
                sb.append(error.getDefaultMessage()).append("<br/>");
            }
            r = R.error(sb.toString());
        } else {
            r = R.error();
        }
        return r;
    }
}

返回值封装

public class R extends HashMap, Object> implements Serializable {
 private static final int DEFAULT_ERROR_CODE = 500;
 private static final int DEFAULT_SUCCESS_CODE = 0;
 private static final long serialVersionUID = 1572870614935320893L;

 public R() {
        put("code", DEFAULT_SUCCESS_CODE);
  }

    public static R error() {
        return error(DEFAULT_ERROR_CODE, "程序异常,请联系管理员");
  }

    public static R error(String msg) {
        return error(DEFAULT_ERROR_CODE, msg);
  }

    public static R error(int code, String msg) {
        R r = new R();
  r.put("code", code);
  r.put("msg", msg);
 return r;
  }

    public static R error(String code, String msg) {
        R r = new R();
  r.put("code", code);
  r.put("msg", msg);
 return r;
  }

    public static R ok(String msg) {
        R r = new R();
  r.put("code", DEFAULT_SUCCESS_CODE);
  r.put("msg", msg);
 return r;
  }

    public static R ok(Map, Object> map) {
        R r = new R();
  r.putAll(map);
 return r;
  }

    public static R ok() {
        return new R();
  }

    @Override
  public R put(String key, Object value) {
        super.put(key, value);
 return this;  }

    public R put(Object value) {
        super.put("data", value);
 return this;  }
}

xml配置

<bean class="com.xxx.utils.GlobalExceptionHandler"/>

RPC服务

在提供者,定义异常拦截器

@Activate(group = Constants.PROVIDER)
public class ExceptionFilter implements Filter {

    private final Logger logger;

    public ExceptionFilter() {
        this(LoggerFactory.getLogger(ExceptionFilter.class));
    }

    public ExceptionFilter(Logger logger) {
        this.logger = logger;
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Result result = invoker.invoke(invocation);
        if (result.hasException()) {
            Throwable throwable = result.getException();
            if (throwable instanceof RRException) {
                RRException ex = (RRException) throwable;
                return new RpcResult(ex);
            }
        }
        return result;
    }

}

在META-INF.dubbo下的com.alibaba.dubbo.rpc.Filter文件里面定义

exceptionFilter=com.xxx.solar.ExceptionFilter

异常设计

通过包名来区分是业务异常还是系统异常:
业务异常包名:com.xxx.xxx.exceptions.biz
系统异常包名:com.xxx.xxx.exceptions.system

imagepng
RRException输出error日志
WarnException输出warn日志
如果需要报警,那么可以实现RRException异常类
如果不需要进行报警,那么可以实现WarnException异常类
错误类的定义建议使用枚举,不要定义在常量类中定义code。
PS:ELK只会对error occurs:进行拦截。

也可以关注我的公众号:程序之声

关注公众号,回复:下载
获取百度下载神器:
imagepng

本文为博主原创文章,未经博主允许不得转载。

评论

发表评论