异常处理SOP"/>
Java异常处理SOP
文章目录
- 一、常见异常处理方式
- 二.try catch
- 1.catch类型
- 2 插入数据的时候,需要更加精细的判断是为幂等冲突异常么
- 3. catch的作用
- 4.异常处理sop
- 5、throw new Exception中添加信息
- 5、捕获异常的粒度
一、常见异常处理方式
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}//... }
当我们面对函数抛出异常的时候,应该选择上面的哪种处理方式呢?三个参考原则:
- 如果func1()抛出的异常是可以恢复,且func2()的调用方并不关心此异常(弱依赖),我们完全可以在func2()内将func1()抛出的异常吞掉;
- 如果func1()抛出的异常对func2()的调用方来说,强依赖,我们可以选择直接将func1抛出的异常re-throw;
- 如果func1()抛出的异常太底层,对func2()的调用方来说,缺乏背景去理解、且业务概念上无关,我们可以将它重新包装成调用方可以理解的新异常,然后re-throw
- 补充:网关层catch住异常的时候,可以不打error日志,因为很多网关层的方法都有重试3次,如果都是time out,则会打三个error信息,造成噪音干扰。方式一:网关层catch住异常,不打error只往上抛异常,调用方自己catch打异常信息。方式二:使用spring的retry注解,使用recover自定义方法,在原本网关方法内只抛不打error,在recover方法中打error,这样如果第一次、二次失败了,第三次成功了,则无error日志,如果三次全部失败,才有会打error日志
@Resource
private ProcesstService processtService;@Retryable(recover = "recoverSubmit")public String submit(BillSubmitRequest req) {try {BillSubmitResponse resp = processtService.submit(req);int code = resp.getErrorCode();if (code != 0) {throw new GatewayException(ExceptionCodeConstant.GATEWAY_EXCEPTION_CODE,"创建流程失败,resp:" + GsonUtils.toJsonStr(resp));}return resp.getBillNo();} catch (TException e) {throw new GatewayException(ExceptionCodeConstant.GATEWAY_EXCEPTION_CODE, "创建流程发生异常", e);}}@Recoverpublic String recoverSubmit(Exception e, BillSubmitRequest req) throws Exception {log.error("创建流程异常,req:[{}]", GsonUtils.toJsonStr(req), e);throw e;}
二.try catch
1.catch类型
-
业务内部逻辑校验、处理等
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());}
2 插入数据的时候,需要更加精细的判断是为幂等冲突异常么
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);}}});
3. catch的作用
-
作用:是用来捕获异常的
-
若先用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
4.异常处理sop
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块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。
5、throw new Exception中添加信息
throw new GatewayException(500,String.format("xxxx失败,req:[%s]", GsonUtils.toJsonStr(day+" "+id+" " +type)), e)
5、捕获异常的粒度
1、多仓,异常粒度要小到单仓
2、并发查询200、200,异常处理要小到每一次查询
public void test(){ExecutorService pool = Executors.newFixedThreadPool(3);List<Long> poiIdList = Lists.newArrayList(1L, 2L, 3L);List<String> result = poiIdList .stream().map(poiId -> CompletableFuture.supplyAsync(() -> {try {return queryPoiNameById(poiId);} catch (Exception e) {System.out.println(e);}return new ArrayList<String>();}, pool)).collect(Collectors.toList()).stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList());System.out.println(result);}public List<String> queryPoiNameById(Long poiId) {return Lists.newArrayList("-" + poiId);}
更多推荐
Java异常处理SOP
发布评论