古时的风筝原创系列
API (Application Programming Interface)对我们来说那简直太熟悉了,整个编程过程充满了各种 API 的调用,无论是系统 API、JDK API、同一项目下 service 层 API,还是第三方 API,例如 RESTful 接口。
我们这里姑且分为内部用 API 和外部用 API。内部用可以理解为本团队使用,可以将完整异常信息暴露出去的 API。外部用可以理解为供本团队外的开发者使用,例如 RESTful 接口、dubbo 接口,不希望暴露完整的异常出去的 API。
不论是哪种情况,正确的处理 API 产生的异常都是非常重要的,更好的处理异常,可能帮我们更快速的定位问题以及指引使用者正确的使用 API。
相信有不少同学碰到过异常处理不到位产生的问题,比如我就碰到过一个让人哭笑不得的异常处理。
很早之前,我刚接手一个Spring MVC 项目,也没什么说明文档,说实话,项目也不是很复杂,就是服务多一点而已。我在本机启动,先启动了几个 dubbo 服务,然后启动 UI 服务(没有前后端分离,用的是 freemarker),启动也正常,然后开心的访问登录页,结果来到了一个令人难过的页面 404 页面。好吧,有问题就解决,开发过程中,碰到问题才正常,碰不到问题反而有点奇怪,于是乎,开始寻找问题,404 一般来说大概率是配置有问题,于是开始查配置文件,看是不是有的地方配置不通,但是,经过一番查找还是没发现问题,这就有点不对劲了,那肯定是有什么特殊的配置,翻了一遍文档,但是这文档写的,有和没有并没有多大的差别。咨询了一下之前的开发同事,回复是好像没有什么特殊的配置。
那好吧,debug 一下,后台 Controller 接口还没执行到就报错了,那肯定是有全局异常捕获或者有拦截器之类的,没错,所有 Controller 都继承了 BaseController ,而 BaseController 里有全局异常捕获的方法,最奇葩的地方来了,捕到异常直接返回 404,这也可以,这是没开发完还是故意迷惑对手。我对这波操作表示由衷的佩服。
而为什么会报异常,那又是另一个故事了,简单来说就是,用户认证服务用了一个第三方的组件,这个组件可以配置域名白名单,必须在白名单里的域名才能正常请求,所以本机调试必须要配置上 host 对应上白名单里的域名才可以。话说这么重要的信息不应该写作文档里吗。
那 MVC 分层架构来说,Controller、Service、Dao 层可能由不同的人开发,那不同层之间的调用,或者同一项目中不同模块之间的调用,或者一些提供公共方法的包等,都可以理解为内部 API 调用。
内部用的 API 处理起来比较灵活,每个团队可能都有不同的标准。共同的一点是,异常都可以完全的暴露出去给调用方,当然,也可以选择在 API 方法中自行处理。
内部用 API 异常处理应该遵循以下准则:
1、所有准则中,有一条是必须要遵守的,那就是异常捕获了就必须处理,不能抓了异常后默默把异常吃掉。不知道你有没有碰到过这种情况,异常抓了连日志都不打一下。
2、如果捕获了异常不想自己处理,那应该抛出去,让调用方自行处理。
3、避免把大段代码都放到 try... catch 中,应该尽可能的只包括住可能发生异常的代码。
4、try...catch 中如果包含异常后要回滚的操作,一定要手动回滚,不要给调用方带来不必要的麻烦。
5、尽量不要直接抛 Excepiton 或 Throwable,应该使用业务相关的自定义异常类。例如定义好 DaoException、ServiceException。
例如下面这个 ServiceExcepiton 类的定义,有错误信息和异常码。
public class ServiceException extends RuntimeException {
public ServiceException(String message){
super(message);
}
public ServiceException(String message,Throwable cause){
super(message,cause);
}
public ServiceException(String message, String errorCode){
this(message);
this.errorCode = errorCode;
}
public ServiceException(String message, String errorCode, Throwable cause){
this(message,cause);
this.errorCode = errorCode;
}
private String errorCode;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
}
另外,内部不同模块,不同系统之间的调用有可能要通过 dubbo 或者其他 HTTP 或者非 HTTP 的 RPC 调用,可以参考下面的外部 API 异常处理方式。
对于外部用的 API ,一般采用 HTTP 方式,直接返回完整异常信息肯定是不可取的。对调用方来说没什么用处。二来也不是很安全,完整的堆栈信息容易暴露过多的服务器信息。另外,堆栈信息一般都很大,会造成网络传输等方面的性能损耗。
如果你做过公众号开发,那肯定知道微信开发文档里定义的全局返回码。
提供给外部使用的 API 一般要遵循如下准则:
1、定义统一的返回格式,不能同一套系统中开放出去的接口返回格式不一致,这就很不专业了,一般由一个状态码表示返回成功或异常。
内部可以定义一个 Result 泛型类来实现统一的格式输出。
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
private T result;
private String code;
private String message;
private boolean success;
private Result() {
}
private Result(T result, String code, String message, boolean success) {
this.result = result;
this.code = code;
this.message = message;
this.success = success;
}
public static <T> Result<T> ok(T result) {
return new Result(result, null, null, true);
}
public static <T> Result<T> fail(String code, String message) {
return new Result((Object)null, code, message, false);
}
}
2、定义与状态码对应的异常信息提示,一定要能准确的表示异常发生的原因,帮助使用者定位问题。
异常状态码和描述信息可以通过一个枚举类来实现
public enum ErrorCodeEnum {
USER_NOT_EXIST("用户不存在", 400001),
PARAMS_PART_ID_ERROR("参数 partId 错误", 400002);
private String message;
private int code;
private ErrorCodeEnum(String message, int code) {
this.message = message;
this.code = code;
}
public String getMessage() {
return this.message;
}
public int getCode(){
return this.code;
}
}
3、提供完备的接口文档,其中对应异常部分就是状态码对应的信息描述说明,例如上图微信开发者文档。
以上的异常处理实际上就是团队中的代码规范,每个公司、每个团队可能会有不同,但基本原则都类似。异常处理只是项目中很细节的问题,但往往很多问题都出在细节上,看似简单,但却要提早规范,引起重视。
有一些准则直接参考了 《阿里巴巴Java开发手册(终极版)》,没读过的同学可以到网上搜索一份,也可以在本公众号内回复 「 阿里巴巴 」获取下载链接。
隔离做的好,数据操作没烦恼[MySQL]
Spring Cloud 系列吐血总结
Java 调试工具、热部署、JVM 监控工具都用到了它
公众号:古时的风筝
一个斜杠程序员,一个纯粹的技术公众号,多写 Java 相关技术文章,不排除会写其他内容。
【细节是魔鬼!】