1. Result的使用

Result的使用,是java项目中开发接口的必备,它经常被我们用作接口的返回对象,方便前端或者其他程序的远程调用后处理业务。它一般包括以下几个属性:

  • code:一般根据系统设定,在接口调用中返回代表含义不同的编码(例如:code=2000代表接口调用正常,code=1000代表无权限 等等),而且项目中会有一个集中设定code的配置文件,方便根据编码查询错误具体的错误信息
  • message:调用接口时,返回的信息。一般接口正常调用返回"成功",调用失败返回失败原因,或者直接返回异常信息
  • data:调用接口后返回的数据,一般为使用 泛型T。例如:查询接口,data中即存放查询出来的数据。

Result.java

public class Result<T> implements Serializable {
// 标识代码,2000表示成功,其它数值表示出错
private int code;
// 返回的数据
private T data;
// 提示信息,供报错时使用
private String message;
// 返回时间
private String timestamp;
// 有参构造
public Result(int code, String message, T data) {
this.timestamp = String.valueOf(System.currentTimeMillis());
this.code = code;
this.data = data;
this.message = message;
this.traceId = traceId;
} // sucess、error两个方法方便Result的使用
public static <T> Result<T> success(T data) {
return new Result(ResultCode.OK.getCode(), ResultCode.OK.getMessage(), data);
}
public static <T> Result<T> error(ResultCode code) {
return new Result(code.getCode(), code.getMessage(), null);
}
}

ResultCode.java 用于定于各种状态码 以及 状态码的具体业务含义的描述

public enum ResultCode  {
UNAUTHORIZED(1000, "未经授权,无法访问"),
OK(2000, "成功"),
; private int code;
private String message; ResultCode(int code, String message) {
this.code = code;
this.message = message;
} public int getCode() {
return code;
} public String getMessage() {
return message;
}
}

2. 存在的问题

当我们在享受Result给我们带来的便利的同时,也在许多场景困惑于它带来的"麻烦"。java的远程调用遵循:使用起来要像在本地方法调用的感觉一样。

举个栗子

一个根据id查询用户的例子,使用Result作为返回结果

// 提供服务方
public interface UserService {
Result<User> getUserById(Long userId);
} // 调用例子
public User testGetUser(Long userId) {
String userKey = "userid-" + userId;
// 先查缓存,如果命中则返回缓存中的user
// cacheManager.get(123, userKey);
// ... try{
Result<User> reslut = userService.getUserById(userId);
if(result.isSuccess()) {
cacheManager.put(123, userKey, result.getData());
return reslut.getData();
}
// 否则清空缓存对象,代表用户不存在
cacheManager.put(123, userKey, NullCacheObject.getInstance(), 3600);
return null;
} catch(Exception e) {
// TODO log
throw new DemoException("getUserById error, userId:" + userId, e);
}
}

从result.isSuccess为fasle的地方思考,我们并分不清报错的实际含义:是因为网络异常、DB错误等系统错误导致、或是因为用户不存在等业务错误导致,从Result中取得的错误码,还要去服务方提供的状态码文档中去查找分析,如果是服务端系统异常,那么15行将导致后续1小时对该用户的请求都认为用户不存在。

严谨点的写法:

// 调用例子
public User testGetUser(Long userId) {
String userKey = "userid-" + userId;
// 先查缓存,如果命中则返回缓存中的user
// cacheManager.get(123, userKey);
// ... try{
Result<User> reslut = userService.getUserById(userId);
if(result.isSuccess()) {
cacheManager.put(123, userKey, result.getData());
return reslut.getData();
}
try{
if("USER_NOT_FOUND".equals(result.getCode())) {
// 清空缓存对象,代表用户不存在
cacheManager.put(123, userKey, NullCacheObject.getInstance(), 3600);
} else {
// 可能是SYSTEM_ERROE、DB_ERROR等系统异常
throw new DemoException("getUserById error, userId:" + userId + ",result=" + result );
}
} catch(DemoExpcetion e) {
throw e;
}
} catch(Exception e) {
// TODO log
throw new DemoException("getUserById error, userId:" + userId, e);
}
return null;
}

代码变得复杂起来。

下面是接口提供方直接将系统异常抛出,将业务异常封装的写法

public interface UserService{
User getUserById(Long userId) throws DemoAppException;
} // 调用例子
public User testGetUser(Long userId) {
String userKey = "userid-" + userId;
// 先查缓存,如果命中则返回缓存中的user
// cacheManager.get(123, userKey);
// ... try{
User user = userService.getUserById(userId);
if(user != null) {
cacheManager.put(123, userKey, user);
return user;
}
} catch(Exception e) {
// TODO log
throw new DemoException("getUserById error, userId:" + userId, e);
}
return null;
}

这样代码会比较简介,而且符合我们的调用习惯:是远程调用异常处理起来像本地异常一样,不然会导致异常的处理很混乱、复杂。


3. 总结

业务异常 与 系统异常要分清并不同化处理

  • 业务异常:例如:无权限、无此用户等,调用方会根据返回的不同异常code处理不同的业务,无权限会提醒"请分配权限",无此用户会提醒"先注册,再使用系统",此类异常封装为固定code是有价值所在,方便调用方。
  • 系统异常:例如:网络超时、DB异常等问题,对于此类异常掉用方并无能力处理此类异常,即使我们将此类异常用ResultCode封装起来后,其实调用方并不会根据我们返回的不同code去具体处理业务,而是用来日志定位、问题追踪。对于这类问题如果直接根据有无业务结果来判断,直接抛异常比Result封装异常后理解和使用上的更直接。同时也减少调用的资源开支。这个时候,我们在开发过程中就要能够准确地预测掉用方在接收到我们封装的异常后,会如何处理。

--该文章参考自《阿里淘系-2021技术人的百宝黑皮书》

java 如何正确使用接口返回对象Result的更多相关文章

  1. Java 利用枚举封装接口返回 JSON 结构

    利用枚举封装返回码和返回信息 package com.template.project.util; public enum StatusCode { SUCCESS(20000, "成功&q ...

  2. WebAPI接口返回ArrayList包含Dictionary对象正确解析

    一.问题提出 为了减少流量,将key-value(键值对)直接输出到Dictionary<string, string>,接口返回结果如下: 其中{}里面内容如下: 上图显示600是键,4 ...

  3. JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法

    在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city. public class Address { priva ...

  4. Effective Java 第三版——64. 通过对象的接口引用对象

    Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...

  5. java 多线程:Callable接口;FutureTask类实现对象【Thread、Runnable、Callable三种方式实现多线程的区别】

    Callable接口介绍: Java5开始,Java提供了Callable接口,像是Runnable接口的增强版,Callable接口提供了一个 call()方法可以作为线执行体. call()方法比 ...

  6. Java的Object.hashCode()的返回值到底是不是对象内存地址?

    关于这个问题,查阅了网上的资料,发现证明过程太繁琐,这里我用了反证法. java.lang.Object.hashCode()的返回值到底是不是对象内存地址? hashCode契约 说到这个问题,大家 ...

  7. 《Java核心技术》 -- 读书笔记 ② - 类 | 对象 | 接口

    对象vs对象变量 “对象” 描述的是一个类的具体实例,他被java虚拟机分配在 "堆" (Heap)中. “对象变量” 为一个对象的引用(对象变量的值=记载着具体对象的位置/地址) ...

  8. java解析从接口获取的json内容并写到excle(只写与标题匹配的值,并非把所有的接口返回值都写进去)

    需求:从接口中获取的一个json数组中有多个对象,每个对象中的值并非都需要,只需查出标题中的几项对应的值即可.且还需要按某个字段排序后依次写到excel 实现方法如下: package jansonD ...

  9. 为什么阿里巴巴Java开发手册中强制要求接口返回值不允许使用枚举?

    在阅读<阿里巴巴Java开发手册>时,发现有一条关于二方库依赖中接口返回值不允许使用枚举类型的规约,具体内容如下: 在谈论为什么之前先来科普下什么是二方库,二方库也称作二方包,一般指公司内 ...

  10. Java中的Serializable接口transient关键字,及字节、字符、对象IO

    1.什么是序列化和反序列化Serialization是一种将对象转为为字节流的过程:deserialization是将字节流恢复为对象的过程. 2.什么情况下需要序列化a)当你想把的内存中的对象保存到 ...

随机推荐

  1. Kubernetes 控制器

    在实际使用的时候并不会直接使用 Pod,而是会使用各种控制器来满足我们的需求,Kubernetes 中运行了一系列控制器来确保集群的当前状态与期望状态保持一致,它们就是 Kubernetes 的大脑. ...

  2. windows系统下使用bat脚本文件设置 tomcat 系统环境变量

    说明:在一个bat文件中设置tomcat环境变量后,不能直接使用,需要另起一个bat文件才能使用 号开头的行不要写在bat文件中 # tomcat1.bat # 这个bat文件实现的功能:设置环境变量 ...

  3. Prometheus组件介绍

    Prometheus Server Prometheus Server是Prometheus组件中的核心部分,负责实现对监控数据的获取,存储以及查询. Prometheus Server可以通过静态配 ...

  4. C++ 自学笔记 对象的初始化

    数组的初始化: 在 C++中  struct ≈ Class:struct里面可以有函数. 默认构造函数: 没有参数的构造函数就是默认构造函数

  5. springboot自动配置原理以及手动实现配置类

    springboot自动配置原理以及手动实现配置类 1.原理 spring有一个思想是"约定大于配置". 配置类自动配置可以帮助开发人员更加专注于业务逻辑开发,springboot ...

  6. hive数据导出到linux本地

    方法1(hive下执行):insert overwrite local directory 'Linux本地目录' row format delimited fields terminated by  ...

  7. 拉格朗日插值优化DP

    拉格朗日插值优化DP 模拟赛出现神秘插值,太难啦!! 回忆拉格朗日插值是用来做什么的 对于一个多项式\(F(x)\),如果已知它的次数为\(m - 1\),且已知\(m\)个点值,那么可以得到 \[F ...

  8. tensorboard图表显示不全的问题

    之前跑bcq生成tensorboard文件的时候,有二十个点用来描图,然而后10个数据点总是不显示,之后将tensorboard换成tensorboardX便解决了问题. 比如 from torch. ...

  9. 27.-Django发送邮件

    一.邮件相关协议-SMTP SMTP全称是Simple Mail Transfer Protocol,即简单邮件传输协议(25端口号) 它是由一组从源地址到目的地址传输邮件的规范,通过它来控制邮件的中 ...

  10. 22.-CSRF攻击

    一.CSRF-跨站伪造请求攻击 某些恶意网站上包含链接.表单按钮或者JavaScript,它们会利用登录过的用户在浏览器中的认证信息视图在你的网站上完成某些操作 这就是跨站请求伪造(CSRF,即Cro ...