此文已由作者占金武授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

先说明一下背景:项目日志中的Exception会被哨兵统一监控并报警

比较多的项目基于dubbo在做服务化

表单参数校验中异常使用的建议

异常机制存在的一个最大好处是让JAVA函数实现了“多返回值”,比如:public int caculate(int a, int b) throws MyException {

}

这段代码的本质是让函数caculate拥有了这样一个返回值[int, MyException],这样做有什么好处呢?

假设不使用异常,上面的函数只能用-1、-2这类魔法数来表达异常情况,这样做会比较糟糕,因为使用这个函数的人必须非常小心地去处理返回值里的这些魔法数,而时常这是一件容易遗漏的事。

这样看来,在进行入参检验的时候,发现不合法参数而返回IllegalArgumentException是非常合理且自然的用法。

结合一下web表单场景,假设这里是对用户输入参数的校验,后台校验不合法,由MVC的Controller层统一汇总封装返回给前端会是一种比较优雅的做法,而前端要做的是配合后端的返回的数据结构把有用的错误信息展示给用户。再结合前面提到的背景,这里出现的异常不应该打印堆栈日志,否则会造成哨兵误报(之所以说是误报是因为这是一种常见情况不应该引起运维人员的注意并介入处理),建议的做法是记录相应的异常日志。

这里我们有必要再思考清楚一些,如果没有哨兵报警误报的问题,我们是否有必要打印堆栈日志呢?一般而言,打印异常堆栈是为了帮助运维人员(或开发人员)迅速定位异常原因,进而修复异常。而这里的场景其实是不需要运维人员介入的,由前端页面提示给用户,用户调整相应的参数后重新发起请求即可恢复。

说得有点啰嗦,但其实是为了更清楚的强调这样一个观点:使用异常并不代表一定要把堆栈打印出来,比如web表单入参的检测

dubbo接口中异常使用的建议

先抛出几个dubbo异常相关的常见问题:

dubbo provider方法的实现底层使用了自定义的XXRuntimeException,在api jar中并未包含此XXRuntimeException定义,consumer调用发现无法识别XXRuntimeException,提示“Got unchecked and undeclared exception...”

dubbo provider方法的实现底层使用了IllegalArgumentException,consumber调用产生IllegalArgumentException,而provider并未发现自己系统产生了这些异常(比较典型的情况是provider的数据库连接异常),也没有相应的监控,只能等到consumber来投诉。

以上两个问题的产生与 dubbo 的实现有关,来看看 dubbo 是怎么处理异常的(ExceptionFilter):

Result result = invoker.invoke(invocation);if (result.hasException() && GenericService.class != invoker.getInterface()) {    try {

Throwable exception = result.getException();        // 如果是checked异常,直接抛出

if (! (exception instanceof RuntimeException) && (exception instanceof Exception)) {            return result;

}        // 在方法签名上有声明,直接抛出

try {

Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes());

Class>[] exceptionClassses = method.getExceptionTypes();            for (Class> exceptionClass : exceptionClassses) {                if (exception.getClass().equals(exceptionClass)) {                    return result;

}

}

} catch (NoSuchMethodException e) {            return result;

}        // 未在方法签名上定义的异常,在服务器端打印ERROR日志

logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()

+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()

+ ", exception: " + exception.getClass().getName() + ": " + exception.getMessage(), exception);

...

从以上实现可以看出,如果unchecked异常未显示声明,则会自动打印error日志。

那该如何优雅地解决呢?

dubbo接口要避免向外抛出RuntimeException(不仅仅是为了避免扰人的error提醒,更为了避免异常泄漏)。建议的一种方法是在接口实现代码的最外层统一使用类似以下示例的方式进行包装:

Response r;try {

r = ...;

} catch (RuntimeException e) {

logger.error("error msg", e);//相当于单个项目下异常处理的最外层,需要把异常记录下来

r = Response.buildErrorResponse(e.getMessage());

}return r;

如果一定要使用异常来表达接口语义,使用Checked Exception

使用异常 vs 使用null

前面已经提到,异常的使用会带来编码的便利,但同时也为更多的无用日志输出埋下了隐患。就上面包装代码的例子,如果RuntimeException是网络连接或者数据库连接异常倒还好,属于有用的异常,但如果是因为某项数据不存在而抛出IllegalArgumentException,则日志就显得有点多余了(无法根据日志内容采取有效的行动来阻止)。

public Permission loadPermission(Long userId) {    if(...) {        return ...

} else {        throw new IllegalArgumentException();// or throw new NotFoundException();

}

}

这种写法下,loadPermission需要 try{...}catch(){...}的额外‘照应’才得处理得当,是不是有点繁琐呢?此时直接返回null可能来得更加直接呢?出错了和没有其实是两回事,应该仔细斟酌。

所以,这里我给出的建议是:如果能简单方便地避免使用异常,则避免之。

异常统一处理的建议

前面提到了:与前端交互时的异常处理、dubbo接口中的异常处理,其中都提到了一点:运行时异常建议统一处理(Checked异常已经强制由程序员进行处理了)。扩展一下,还有哪些异常需要统一处理呢?是以什么样的维度进行统一呢?我理解可以围绕线程用途进行聚合处理。常见的WEB系统中一般有以下几类线程在运行:

主线程(Main函数,异常交由JVM处理)

HttpRequest线程(一般由Controller提供的hook方法一处理)

异步任务线程池中的工作线程(一般由线程池提供hook接口方法进行统一处理)

dubbo线程(由ExceptionFilter进行统一处理)

以上思路理清以后,大家就可以参照进行异常的统一处理了。

Spring MVC:@ExceptionHandlerpublic Object exception(Exception exception, HttpServletRequest request, HttpServletResponse response) {    return ExceptionUtil.processException(request, response, exception, logger);

}

线程池:ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(11, 100, 1, TimeUnit.MINUTES, //

new ArrayBlockingQueue(10000),//

new DefaultThreadFactory()) {

protected void afterExecute(Runnable r, Throwable t) {

super.afterExecute(r, t);

printException(r, t);

}

};

dubbo:public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException {

Result result = invoker.invoke(invocation);    if(result.hasException()) {

printException(result.getException());

}    return result;

}

结束语

互联网上关于JAVA异常使用和最佳实践的文章比较多,大多流于理论,缺乏对实际工作的指导意义,缺少对常见应用场景如web层、dubbo接口层的实践讨论。本文试图结合实际应用描述异常使用场景,展开了工程学上的最佳异常实践探索。由于水平有限,内容难免出现理解上的偏差,还请大家批评指正。

更多网易技术、产品、运营经验分享请点击。

异想维度 java_JAVA异常的最佳工程学实践探索相关推荐

  1. 异想维度 java_Java实现多字段(维度)复杂排序

    //Java 实现多字段排序 HashMap map1 = new HashMap(); map1.put("dataindex0", null);map1.put("d ...

  2. Pycurl的简单使用与对比 - 一只橘子的异想世界

    Pycurl的简单使用与对比 - 一只橘子的异想世界 Pycurl的简单使用与对比 - 一只橘子的异想世界 Pycurl的简单使用与对比 十二月 5th, 2010 标签: python,pycurl ...

  3. HTML网页猫咪主题,炫酷好玩的猫咪主题沉浸式装置艺术展:《猫的异想世界》...

    国内首个猫咪主题的沉浸式酷玩展--<猫的异想世界>Meow Meow Land 酷玩展,国际领先的多媒体技术加持,国内原创动画,顶级声音设计,现场真猫在场,零距离吸猫! <猫的异想世 ...

  4. java 异想_异想家博客图片批量压缩程序

    为了方便给自己的博客配图,用Golang写了一个脚本处理,现分享出来,有需要的朋友也可以参考修改使用. 压缩规则 1.图片都等比例压缩,不破坏长宽比. 2.如果是横屏图片,压缩到宽度为1280,高度适 ...

  5. [奇思异想]使用Zookeeper管理数据库连接串

    背景 有一套特定规格的应用(程序+数据库),当有业务需求时,就需要多部署应用,并且所有的应用都使用一个共同的后台来管理.应用新增后,如何通知后台更新连接串成了一个关键的问题.于是就产生了使用ZooKe ...

  6. 054_《奇思异想编程序Delphi篇》

    <奇思异想编程序Delphi篇> Delphi 教程 系列书籍 (054) <奇思异想编程序Delphi篇> 网友(邦)整理 EMail: shuaihj@163.com 下载 ...

  7. 【SDCC 2016】电商架构专题干货七连发:探秘知名电商架构最佳技术实践

    [CSDN现场报道]2016年11月18日-20日,由CSDN重磅打造的年终技术盛会 -- "2016中国软件开发者大会"(Software Developer Conferenc ...

  8. [译] 最佳安全实践:在 Java 和 Android 中使用 AES 进行对称加密

    原文地址:Security Best Practices: Symmetric Encryption with AES in Java and Android 最佳安全实践:在 Java 和 Andr ...

  9. BDTC 2017 | “TOP10大数据应用最佳案例实践”十佳获奖单位精彩分享

    [CSDN现场报道]12月7-9日,由中国计算机学会主办,CCF 大数据专家委员会承办,中国科学院计算技术研究所.中科天玑数据科技股份有限公司.CSDN协办的2017中国大数据技术大会(BDTC 20 ...

最新文章

  1. C++TSL之map容器(悲伤的故事)
  2. Sympy常见多个变量【一行代码创建】
  3. Java Arrays.asList()方法详解
  4. 四十二、深入Java中的文件读取操作
  5. [转]使用npm发布vue组件
  6. 大华的支持rtmp推流吗_海康大华DSS视频拉流-RTSP转RTMP多媒体播放技术
  7. mysql 备用字段_数据库设计之备用字段
  8. virtualenv 的使用 —— PyCharm 与 Jupyter Notebook
  9. 【人才引进】博士补贴75万,硕士补贴20万,这个南方城市,高待遇引才150人!...
  10. 批处理保存windows10开机壁纸
  11. Node.js入门(含NVM、NPM、NVM的安装)-(转载)
  12. matlab 三角函数 积化和差,三角函数积化和差
  13. 图像压缩算法python_Python基于opencv的图像压缩算法实例分析
  14. Java使用Thumbnails实现图片指定大小压缩
  15. Android Studio基础-Activity生命周期与多个Activity跳转
  16. 开发直播APP时,视频图片等上传到七牛云存储的实现流程
  17. 康托尔点集matlab实数,康托尔(Cantor)是如何证明实数集是不可数的
  18. 用华为手机现在还不知道这5种实用功能,几千块白花了,太浪费了
  19. 认知能力训练系统--提升6大认知能力
  20. llvm libLLVMCore源码分析 13 - Other Operators

热门文章

  1. 幸福企业!天九共享开辟全球最短工时
  2. android源代码查看(在线观看,AndroidStudio,SourceInsight)
  3. 【项目三、车牌检测+识别项目】一、CCPD车牌数据集转为YOLOv5格式和LPRNet格式
  4. matlab中trapz的用,trapz是matlab中调用()公式的命令
  5. KVM虚拟化平台搭建
  6. 基于Android Studio开发的旅游记录与分享APP源码,Android旅游路线记录与分享APP源码
  7. ERROR: mmcv_full-1.4.0-cp38-cp38-win_amd64.whl is not a supported wheel on this platform.
  8. 超详细,超易懂tcp的五层协议
  9. hpv(宫颈癌)疫苗科普
  10. 推箱子,12864液晶实物