痛点

最近接手一个老项目,这个项目几经转手,到我这里时,发现代码的可阅读性实在是很差,对于一个有点代码洁癖的我来说,阅读起来实在是很难受。其中一个痛点,现在就拉出来讲讲。该项目需要与另外一个项目进行业务对接,因此有很多HTTP接口要调用。现在项目发送HTTP请求的代码跟业务代码完全合在一起,看起来就像这样:

public void doSomething() {
// do some business operation
...
// send http request
Map<String, String> param = new HashMap<String, String>();
param.put("idCard",idNo);
String requestUrl = serverSettings.getRequestPrefix() + serverSettings.getQueryRegisterStatusUrl();
logger.info("post customerId:{}, param:{},url:{}",customerId,param,requestUrl);
long startMillis = System.currentTimeMillis();
String response = httpClientUtil.doPostJSONSign(requestUrl,param);
long endMillis = System.currentTimeMillis();
logger.info("queryKaolaStatus response:{},time:{}",response,(endMillis-startMillis)/1000+"秒");
QueryStatusRto queryStatusRto = JSONObject.parseObject(response, QueryStatusRto.class); // do other business operation
...
}

上面这段代码如果项目中只出现一次,也许还好,项目中到处都是这种代码的时候,就简直让人崩溃。这段代码让本来就复杂的业务代码更难去阅读和维护,也无法进行复用,如果项目中另外一段逻辑中需要调用这个HTTP接口,只能将这段HTTP请求的代码copy过去,造成代码重复。维护过项目的相信都会对重复代码生出感慨,修改重复代码简直就是噩梦,必须小心谨慎,不能遗漏任何一处。

这段代码还有一个问题,就是不方便进行测试联调。对于HTTP接口调用来说,针对每个接口编写一个联调测试类是很有必要的,开户初期用于联调,后期接口参数有改动时,能够很方便进行测试。

总结起来上面这段代码存在的问题有:

  1. 代码可阅读性差,不根据上下文的代码逻辑,可能都没法确定调用的是哪个接口。
  2. 代码可维护性查,后期如果要更改接口参数,到处都要修改,工作量大而且容易出错,不容易进行单元测试。

优化方案

很明显,要优化这段代码,就是要把这段代码抽取到同一个地方去,把面向对象中“抽象”和“封装”的思想运用起来。上面的HttpClientUtil类实际上是在尝试做这个工作,只是封装的不够彻底,把日志仍然遗留在业务代码里面。我们尝试第一个优化版本,把日志从业务代码中移除,放到httpClientUtil.doPostJSONSign()方法中去,具体代码如下:

public void doSomething() {
// do some business operation
...
// send http request
Map<String, String> param = new HashMap<String, String>();
param.put("idCard",idNo);
String requestUrl = serverSettings.getRequestPrefix() + serverSettings.getQueryRegisterStatusUrl();
String response = httpClientUtil.doPostJSONSign(requestUrl,param);
QueryStatusRto queryStatusRto = JSONObject.parseObject(response, QueryStatusRto.class); // do other business operation
...
}

这样代码是不是清晰多了?一下子少了四行代码,如果系统很多地方都有这段代码,一下子就少了很多行代码,节约了阅读代码的时间。当然了,这没什么技术含量,大部分开发都会这么做,写下这段代码的那个同事除外,他对代码的要求很低。这个优化方案给我们减少了大量代码,尽管只抽取了四行日志代码。

不过这个方案还不够面向对象,一方面参数用Map进行存储,参数少还好,参数一旦多起来就不好维护。另一方面,调用HTTP请求这个操作应该要封装起来,requestUrl这个参数(也就是接口url地址)目前是在业务代码中拼接的,这也意味着业务代码中每调用一次就要拼接一次。想办法将这部分代码也抽象出去,于是有了接下来的这个方案。

public void doSomething() {
// do some business operations
...
// send http request
QueryStatusReq req = new QueryStatusReq();
req.setIdCard(idNo);
QueryStatusRto queryStatusRto = queryStatusProxy.queryStatus(req);
// do other business operations
...
}

怎么样?是不是很简洁,具体做了什么事情也是一目了然。代码变得容易看懂多了,单元测试也好进行,调用queryStatusProxy.queryStatus(req)方法就好了。接口参数有了变化时,全局搜索QueryStatusReq这个类就能找到所有使用的地方,完全不用担心会遗漏,如果是Map就根本没法使用全局搜索。

具体是怎么实现的呢?将Map请求参数改成对象,这很好理解。QueryStatusProxy才是接下来的重点:

@Component
public class QueryStatusProxy {
@Autowired
private BaseAPIProxy baseAPIProxy; public QueryStatusRto queryStatus(QueryStatusReq req) {
return baseAPIProxy.request("/v1/queryStatus", req, QueryStatusRto.class);
}
}

这里我们运用了代理模式,BaseAPIProxy代理了真正的HTTP调用,QueryStatusProxy只是填入了一些必要参数,所以我们再继续看BaseAPIProxy的代码:

@Component
public class BaseAPIProxy {
private static final Logger logger = LoggerFactory.getLogger(BaseAPIProxy.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private ServerSettings serverSettings; public <T> T request(String url, Object reqBody, Class<T> respType) {
Class api = reqBody.getClass();
String seq = OrderNoUtil.generateOrderNo();
try {
String data = JSONObject.toJSONString(reqBody);
logger.info("====================>request kaola api:{}, seq:{}, data:{}", api, seq, data);
String sign = EncryptUtil
.encrypt(data, "UTF-8", serverSettings.getsKey(),
serverSettings.getIvParameter());
Map<String, String> params = new HashMap<>();
params.put("data", sign);
params.put("platform_id", serverSettings.getPlatformId());
String request = JSONObject.toJSONString(params);
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
HttpEntity<String> formEntity = new HttpEntity<>(request, headers);
String requestUrl = serverSettings.getRequestPrefix() + url;
long begin = System.currentTimeMillis();
logger.info("====================>request api:{}, seq:{}, request:{}", api, seq, request);
String responseBody = restTemplate.postForObject(requestUrl, formEntity, String.class);
long end = System.currentTimeMillis();
logger.info("====================>request api:{}, seq:{}, use time:{}ms", api, seq, end - begin);
logger.info("====================>request api:{}, seq:{}, response:{}", api, seq, responseBody);
return JSONObject.parseObject(responseBody, respType);
} catch (Exception e) {
logger.error("request exception api:{}, seq:{}",api, seq, e);
throw new RuntimeException("request exception");
}
}
}

BaseAPIProxy这个类将项目中所有与发送HTTP请求有关的代码都抽取过来了,那些烦人的重复代码再也不会在业务代码中出现了,开发效率都变高了,有木有?

当然了,这么做会导致系统中出现很多Proxy类,建议给每个Proxy类都取有意义的名字,这样一眼就能看出这个类是哪个接口相关的。

系统间HTTP调用代码封装的更多相关文章

  1. 内部系统间调用client包的封装方法

    1.何为client 公司内部的系统调用,如果采用http直接调用,会非常不方便,而且不规范,接口升级或者变动,系统间的改动也是相当麻烦.所以在系统间采用client的互相调用,调用简单,如下: 获取 ...

  2. WebService与RMI(远程调用方式实现系统间通信)

    前言 本文是<分布式java应用基础与实践>读书笔记:另外参考了此博客,感觉讲的挺好的,尤其是其中如下内容: 另外,消息方式实现系统间通信本文不涉及.RMI则只采用spring RMI框架 ...

  3. 系统间通信(10)——RPC的基本概念

    1.概述 经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的讲解:RPC.在后续的几篇文章中,我们首先讲解R ...

  4. 系统间通信(9)——通信管理与RMI 下篇

    接上文<架构设计:系统间通信(8)--通信管理与RMI 上篇>.之前说过,JDK中的RMI框架在JDK1.1.JDK1.2.JDK1.5.JDK1.6+几个版本中做了较大的调整.以下我们讨 ...

  5. 系统间通信——RPC架构设计

    架构设计:系统间通信(10)——RPC的基本概念 1.概述经过了详细的信息格式.网络IO模型的讲解,并且通过JAVA RMI的讲解进行了预热.从这篇文章开始我们将进入这个系列博文的另一个重点知识体系的 ...

  6. 解析大型.NET ERP系统 高质量.NET代码设计模式

    1 缓存 Cache 系统中大量的用到缓存设计模式,对系统登入之后不变的数据进行缓存,不从数据库中直接读取.耗费一些内存,相比从SQL Server中再次读取数据要划算得多.缓存的基本设计模式参考下面 ...

  7. 系统间通信(8)——通信管理与RMI 上篇

    1.概述 在概述了数据描述格式的基本知识.IO通信模型的基本知识后.我们终于可以进入这个系列博文的重点:系统间通信管理.在这个章节我将通过对RMI的详细介绍,引出一个重要的系统间通信的管理规范RPC, ...

  8. 系统间通信(5)——IO通信模型和JAVA实践 下篇

    7.异步IO 上面两篇文章中,我们分别讲解了阻塞式同步IO.非阻塞式同步IO.多路复用IO 这三种IO模型,以及JAVA对于这三种IO模型的支持.重点说明了IO模型是由操作系统提供支持,且这三种IO模 ...

  9. 系统间通信(3)——IO通信模型和JAVA实践 上篇

    来源:http://blog.csdn.net/yinwenjie 1.全文提要 系统间通信本来是一个很大的概念,我们首先重通信模型开始讲解.在理解了四种通信模型的工作特点和区别后,对于我们后文介绍搭 ...

随机推荐

  1. idea的maven依赖本地jar

    可以手动添加jar,但是idea手动添加jar时,有时候不行. 用maven依赖本地jar方法,感觉比较正规,不会因为自己忘记手动添加jar. 比如这个达梦数据库依赖 <dependency&g ...

  2. php cli传递参数的方法

    php cli传递参数的方法 <pre>$options = "f:g:"; $opts = getopt( $options ); print_r($opts); & ...

  3. use selenium+chromedriver to taobao automatically

    原理 利用chromedriver来驱动chrome进行各种模拟各种行为操作, 然后利用selenium提供的接口来操作chromedriver. 安装ChromeDriver 当然这个的默认前提是你 ...

  4. [转帖]CentOS 7 x64 安装 Ceph

    CentOS 7 x64 安装 Ceph https://www.cnblogs.com/jicki/p/5801712.html 改天尝试一下. k8s 可以使用ceph 作为 pv和pvc的后端存 ...

  5. Mybatis笔记2

    使用Mybatis完成的CRUD操作 个人总结的一些小规律 学习过程中碰到的错误: org.apache.ibatis.exceptions.PersistenceException: ### Err ...

  6. 对比JPA 和Hibernate 和 Mybatis的区别

    1.JPA.Hibernate.Mybatis简单了解 1.JPA:本身是一种ORM规范,不是ORM框架.由各大ORM框架提供实现. 2.Hibernate:目前最流行的ORM框架,设计灵巧,文档丰富 ...

  7. C++ 中的 多态性

    一 .多态性 1.多态性概述:多态是指同样的消息被不同类型的对象接受时导致不同的行为 2.多态实现:编译时的多态:在编译的过程中确定了同名操作的具体对象.   运行时的多态:在程序运行过程中动态地确定 ...

  8. Pycharm(Eclipse)常用快捷键

    在File_Settings_Keymap中可以设置: 确定快捷键模式为Eclipse 看方法的源码:ctrl+鼠标左键 回退之前代码:alt+左键 前进之前代码:alt+右键 调换相邻两行代码位置: ...

  9. jwt 无状态分布式授权

    基于JWT(Json Web Token)的授权方式 JWT 是JSON风格轻量级的授权和身份认证规范,可实现无状态.分布式的Web应用授权: 从客户端请求服务器获取token, 用该token 去访 ...

  10. dll库生成和使用

    抄自http://www.cnblogs.com/fangyukuan/archive/2010/06/20/1761464.html 1. VS2010中新建Win32-Win32项目,输入名称Dl ...