异常总体上分为2类:
- checked exceptions:
可被Java Compiler 检查的异常,可认为是除 RuntimeException类型 之外的其他异常 - unchecked exceptions:
可通过编译,在程序运行时抛出的异常
在项目设计中,我们常常自定义系统的业务异常,当业务上不满足校验时,可以通过抛出业务异常,到 catch 语句中做统一的处理,例如打印日志,返回调用方错误信息等。举例如下:
public class SettleException extends RuntimeException {
private static final long serialVersionUID = -694489771576921331L;
private String code;
public SettleException(String code, String msg) {
super(msg);
this.code = code;
}
public SettleException(RetCode ret) {
super(ret.getDescription());
this.code = ret.getCode();
}
public SettleException(RetCode retMsg, String msg) {
super(msg);
this.code = retMsg.getCode();
}
public SettleException(RetCode retMsg, String msg, Throwable e) {
super(msg, e);
this.code = retMsg.getCode();
}
public String getCode() {
return this.code;
}
public void setCode(String code) {
this.code = code;
}
}
上面的业务异常定义中,有一个类型:RetCode,这个类是定义各种业务码值的,如下所示:
public enum RetCode implements Serializable {
SUCCESS("0000", "操作成功"),
FAILURE("9999", "操作失败"),
INVALID_ARGS("1001", "输入参数格式非法"),
SYSTEM_ERROR("1002", "系统异常"),
NOT_FOUND("1004", "未获取到数据"),
DATA_REPEAT("1005", "业务数据已存在"),
REQUEST_REPEAT("1007", "请求重复"),
UNKNOWN_ERROR("1009", "未知错误");
private String description;
private String code;
private RetCode(String code, String description) {
this.code = code;
this.description = description;
}
public String getDescription() {
return this.description;
}
public String getCode() {
return this.code;
}
}
这样的定义有一个好处:当接口调用返回执行结果时,包含了返回码定义,调用方可以根据返回码做接下来的业务判断,比如返回1007(请求重复)则放弃本次操作,如果返回9999(系统异常)则可以发一条预警的信息出来。
在使用异常的时候,对于遇到的问题我总结了下,大家可以参考:
1.异常定义尽量放在公共模块,至少不要在每个项目的api中自定义:
我们的系统使用的是微服务架构,涉及到多个微服务,有几个微服务在 api 模块各自定义了异常处理类,使用的时候容易发生误用,比如:
PayFacadeImpl.java
@Override
public BaseRes addOrder(PayOrderReq request) {
logger.info("【生成支付单】request={}", JSONObject.toJSONString(request));
BaseRes response = new BaseRes();
try {
response = payBiz.addOrder(request);
} catch (SettleException se) {
logger.info("【生成支付单】业务异常,异常原因=【{}】", se.getMessage());
response.setResult(RetCode.FAILURE, se.getMessage());
} catch (Exception e) {
logger.error("【生成支付单】系统异常,异常原因:", e);
response.setResult(RetCode.FAILURE, "生成支付单系统异常!");
}
return response;
}
在上面的代码中,SettleException使用的是com.xxx.settle.base.SettleException
PayBizImpl.java
public void addOrder(PayOrderReq request) {
//防重校验
PayOrderExample poExample = new PayOrderExample();
poExample.createCriteria().andOrderNoEqualTo(request.getOrderNo());
int count = payOrderDao.countByExample(poExample);
if (count != 0) {
throw new SettleException(RetCode.DATA_REPEAT, "请求数据重复!");
}
.... ....... ......
..... ....... .....
}
上面的代码里抛出的是com.xxx.pay.base.SettleException
【问题】当发生业务异常时,在PayFacadeImpl不能捕获到,因为异常的类型不同!
2. 除特殊情况,能处理的异常,尽量在本服务处理,不要向外抛出:
如果向调用方抛出了异常,会对调用方产生非常坏的影响,如果调用方在调用的时候用try…catch 捕获了异常,可能也不明白异常产生的原因以及如何处理;如果没有捕获,简直就是灾难!所以在提供服务的时候,异常一定要处理后再返回,如果调用别人的接口,以防万一,一定要try…catch处理,以免对自己的业务产生影响。
今天看到一篇这种情况的文章,大家可以看看:https://mp.weixin.qq.com/s/cHmP_rA_k_5E5uXNawbjbw
3. 服务提供方和服务调用方的返回码尽量放在公共的部分里或者不要重复:
A服务 -----> B服务 -----> C服务
如果B服务定义了一套返回码,C服务也定义了一套返回码,如果返回码 1111 在B服务中代表 请求重复 ,在C系统服务中表示 系统异常 ,若B将结果直接返回,那么A服务对于收到的结果一定会作出错误的判断。