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. 静态文件:Static Files

    官方文档地址:https://fastapi.tiangolo.com/zh/tutorial/static-files/ from fastapi import FastAPI from fasta ...

  2. Beats:使用Elastic Stack对Redis监控

  3. 第一章:模型层 - 5:模型的元数据Meta

    模型的元数据,指的是"除了字段外的所有内容",例如排序方式.数据库表名.人类可读的单数或者复数名等等.所有的这些都是非必须的,甚至元数据本身对模型也是非必须的.但是,我要说但是,有 ...

  4. 前端ES6 面试过关宝典

    ES6 部分 Typescript 部分 前端工程面经(节流防抖.https.前端攻击.性能优化...) https://juejin.cn/post/6844903734464495623 ES6面 ...

  5. PyCharm安装PyQt5及其工具(Qt Designer、PyUIC、PyRcc)详细教程

    摘要:Qt是常用的用户界面设计工具,而在Python中则使用PyQt这一工具包,它是Python编程语言和Qt库的成功融合.这篇博文通过图文详细介绍在PyCharm中如何完整优雅地安装配置PyQt5的 ...

  6. 洛谷P6033 [NOIP2004 提高组] 合并果子 加强版 (单调队列)

    数据加强了,原来nlogn的复杂度就不行了...... 首先对原来的n个数排序(注意不能用快排),因为值域是1e5,所以可以开桶排序,开两个队列,一个存原来的n个数(已经满足单增),另一队列存两两合并 ...

  7. Azure DevOps Pipelines部署.Net Core 应用到Kubernetes

    一.    先决条件 1.Azure Repos Git/Git和项目上传 把本地的Net Core项目上传至Azure Repos Git/Git 2.Docker Registry Service ...

  8. Can not set int field xxx to java.lang.Long 错误

    Can not set int field xxx to java.lang.Long 错误 这个错误其实是因为Java程序和MySQL表中字段的属性匹配不一致 我的报错是Can not set ja ...

  9. day01-4-订座功能

    满汉楼01-4 4.功能实现03 4.5订座功能 4.5.1功能说明 如果该餐桌处于已经预定或者就餐状态时,不能进行预定,并给出相应提示 4.5.2思路分析 根据显示界面,要考虑以下两种状态 检测餐桌 ...

  10. PHP redis有序集合实现分页

    <?php //连接本地的 Redis 服务 $redis = new Redis(); $redis->connect('127.0.0.1', 6379); //设置 redis 字符 ...