Service层func2,调用网关层func1
直接吃掉
public void func1() throws Exception1 {
// ...
}
public void func2() {
//...
try {
func1();
} catch(Exception1 e) {
log.warn("...", e); //吐掉:try-catch打印日志
}
//...
}
原封不动地re-throw
public void func1() throws Exception1 {
// ...
}
public void func2() throws Exception1 {//原封不动的re-throw Exception1
//...
func1();
//...
}
包装成新的异常re-throw
public void func1() throws Exception1 {
// ...
}
public void func2() throws Exception2 {
//...
try {
func1();
} catch(Exception1 e) {
throw new Exception2("...", e); // wrap成新的Exception2然后re-throw
}
//...
}
当我们面对函数抛出异常的时候,应该选择上面的哪种处理方式呢?三个参考原则:
业务内部逻辑校验、处理等
catch (BusinessException e) {
log.warn("xxx-内部校验失败~ ", e);
response.setCode(Constants.BUSINESS_ERROR);
response.setMessage(e.getMessage());
}
rpc失败:超时、网络、对方服务等不知道的原因导致的失败,内部异常
catch (Exception e) {
log.error("xxx~ ", e);
response.setCode(Constants.INTERNAL_ERROR);
response.setMessage("系统异常");
}
一次普通的rpc异常判断
try {
} catch (TException e) {
throw new BusinessException(500, true, e.getMessage());
}
Lists.partition(doList, batchThreshold).forEach(partitionData -> {
try {
this.xxxMapper.batchInsert(partitionData);
} catch (Exception e) {
if (e instanceof DuplicateKeyException) {
log.warn("保存xxx数据重复,data:[{}]", GsonUtil.toJson(partitionData), e);
} else {
log.error("保存xxx数据发生异常,data:[{}]", GsonUtil.toJson(partitionData), e);
}
}
});
作用:是用来捕获异常的
若先用Exception捕获,则后面的异常类型都不会走到,一旦发生了异常,直接被最大的Exception捕获了
如果rpc调用结果code != 0 ,则说明rpc调用失败,此时如果后续逻辑强依赖这个rpc结果,而这个结果又没有查出来/错误的,就需要throw异常。如果不是强依赖,比如一品多码,即使错误了,后续流程仍可以正常走,则仅需要log.error打个错误日志即可
一般使用gateway调用的rpc,catch的异常,要看调用的方法,抛出的是TException、还是Exception,对应的catch住,如果需要throw则抛Gateway_error
Business_error和interalError的区别:interan异常主要是一些无法预料的原因导致的rpc失败,比如网络抖动超时等。
catch匹配到异常后,会把异常吃掉。如果你在catch中打了相关信息,没有再向上抛出异常,则异常就在此处被吃掉了。如果是@Trasactional注解,异常就不能被吃掉,就需要在catch中再向上throw,这样事物才能一致。
调用者为前端的时候,如果你不想让前端在调用时抛出红色异常。那么你就不在最外层catch中再次throw一个异常,而是吃掉这个异常,并且给出相应的code值和message即可。打出error日志即可
前提是和前端约定好,不是0code则为异常,前端自己定义错误信息也可以
如果你的接口是给别人调用的(前端、别人)你需要给出rpc异常的,比如TException
1、不要忽略捕捉的异常
catch (NoSuchMethodException e) {
return null;
}
虽然捕捉了异常但是却没有做任何处理,除非你确信这个异常可以忽略,不然不应该这样做。这样会导致外面无法知晓该方法发生了错误,无法确定定位错误原因。
2、在你的方法里抛出定义具体的检查性异常
public void foo() throws Exception { //错误方式
}
推荐:
public void foo() throws SpecificException1, SpecificException2 { //正确方式
}
3、捕获具体的子类而不是捕获 Exception 类
try {
someMethod();
} catch (Exception e) { //错误方式
LOGGER.error("method has failed", e);
}
推荐:
try {
rpc();
} catch (TException e) {
LOGGER.error("method has failed", e);
}
4、始终正确包装自定义异常中的异常,以便堆栈跟踪不会丢失
catch (NoSuchMethodException e) {
throw new MyServiceException("Some information: " + e.getMessage()); //错误方式
}
推荐:
catch (NoSuchMethodException e) {
throw new MyServiceException("Some information: " , e); //正确方式
}
5、要么记录异常要么抛出异常,但不要一起执行
catch (NoSuchMethodException e) {
//错误方式
LOGGER.error("Some information", e);
throw e;
}
正如上面的代码中,记录和抛出异常会在日志文件中产生多条日志消息,代码中存在单个问题,并且对尝试分析日志的同事很不友好。
6、finally 块中永远不要抛出任何异常
7、始终只捕获实际可处理的异常
catch (NoSuchMethodException e) {
throw e; //避免这种情况,因为它没有任何帮助
}
不要为了捕捉异常而捕捉,只有在想要处理异常时才捕捉异常,或者希望在该异常中提供其他上下文信息。如果你不能在 catch 块中处理它,那么最好的建议就是不要只为了重新抛出它而捕获它。
8、不要使用 printStackTrace() 语句或类似的方法
最终别人可能会得到这些堆栈,并且对于如何处理它完全没有任何方法,因为它不会附加任何上下文信息。
9、记住早 throw 晚 catch 原则
应该尽快抛出(throw)异常,并尽可能晚地捕获(catch)它。应该等到有足够的信息来妥善处理它。
10、在异常处理后清理资源
则仍应使用 try-finally 块来清理资源。 在 try 模块里面访问资源,在 finally 里面最后关闭资源。即使在访问资源时发生任何异常,资源也会优雅地关闭。
11、尽早验证用户输入以在请求处理的早期捕获异常
12、一个异常只能包含在一个日志中,在日志文件中这两个日志消息可能会间隔 100 多行。应该这样做:
LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");
推荐:
LOGGER.debug("Using cache sector A, using retry sector B");
13、编写多重catch语句块注意事项:顺序问题:先小后大,即先子类后父类
否则,捕获底层异常类的catch子句将可能会被屏蔽。
14、多层try、catch
throw后面的任何语句不被执行,最邻近的try块用来检查它是否含有一个与异常类型匹配的catch语句。如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
throw new GatewayException(500,
String.format("xxxx失败,req:[%s]", GsonUtils.toJsonStr(day+" "+id+" " +type)), e)