基于动态代理的Java声明式HTTP网络请求框架的实现

2024-12-31 00:00:00龚骏
互联网周刊 2024年16期
关键词:序列化调用开发者

摘要:随着网络技术的发展,越来越多项目需要调用第三方服务接口来获取资源或完成某些功能。使用传统的HTTP客户端框架对接这些差异极大的API,开发和维护成本越来越高。本文提出了一种基于动态代理模式的Java声明式HTTP网络请求框架,利用此框架通过调用Java接口发送HTTP请求的方式,可有效提高开发效率,降低系统维护成本。同时,可通过自定义拦截器和自定义注解来增强框架的扩展能力。

关键字:Java;动态代理;声明式HTTP请求框架;Forest框架

引言

随着网络技术的发展,越来越多的项目需要和第三方企业进行对接,这些服务商提供的接口大多是基于HTTP的OpenAPI[1](open application programming interface),即开放平台接口。但是,每家公司的API都有或多或少的差异。有的遵循RESTful风格[2],有的不遵循任何HTTP规范;有的需要SSL双向认证,有的只需要SSL单向认证;有的以JSON格式进行数据传输,有的以XML格式进行数据传输,类似的细节差异还有很多。

如果使用传统HTTP客户端框架处理这些接口对接工作,会要求开发者从HTTP请求参数、头信息、签名、SSL认证,到数据序列化与反序列化都要自行处理,十分烦琐,并且很容易将HTTP协议相关的细节与业务代码混在一起,增加了代码维护难度和接口管理难度。

本文提出的声明式HTTP请求框架Forest,将HTTP请求代码分为接口定义和接口调用两部分,接口定义部分通过Java注解直观地描述接口方法所对应的HTTP相关内容,接口调用部分则可以像调用普通Java方法一样调用接口,而不必关心HTTP请求的具体细节。这样不但有效解耦HTTP请求代码和其他Java业务代码,还能对开发者屏蔽所有不同HTTP请求之间的差异,提高了代码的健壮性和可维护性,并且使得第三方HTTP接口都被集中在相应的Java接口类中,更容易进行管理和维护。同时,Forest框架会自动进行数据的序列化和反序列化,简化了发送HTTP请求和接收服务端响应数据的过程。

1. Java的HTTP请求相关技术与设计模式

在Java编程领域,当涉及HTTP网络请求的处理时,开发者拥有多样的选择。除了JDK自带的HttpURLConnection类外,众多第三方开源库也提供了强大的支持,其中Apache HttpClient和OkHttp使用尤为广泛,已成了业界的佼佼者。这些库不仅各具特色,而且凭借各自的优势,能够灵活应对各种HTTP网络请求场景。

1.1 Httpclient框架

HttpClient是Apache Jakarta Common下的子项目[3],是目前在Java、Android等领域中使用最广泛的HTTP请求框架之一,其对HTTP协议标准支持较为宽泛,功能也很丰富,但使用起来比较复杂。

1.2 OkHttp框架

OkHttp是一套处理HTTP网络请求的依赖库,由Square公司设计研发并开源,目前可以在Java和Kotlin中使用[4],支持HTTP/2、SPDY等较新的网络协议,但不支持某些非标准的HTTP请求。

1.3 动态代理模式

1994年,四位作者:Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides提出了设计模式的概念,代理模式是他们提出的23种经典设计模式之一[5-6],其核心作用是为其他对象提供一种代理机制,从而实现对目标对象的访问控制。在某些特定场景下,当调用者无法或不愿直接引用某个对象时,可以借助“代理”这一中介者来实现间接引用。

代理模式主要分为静态代理和动态代理两种形式。静态代理要求在编译阶段就预先定义好代理类,并通过这些代理类间接地调用被代理的接口。然而,当需要代理的接口数量庞大时,这种方式就需要准备相应数量的代理类。

动态代理则是利用Java的反射机制,在运行时动态地创建并生成指定接口的代理对象,每个代理对象都会一个相关联的InvocationHandler接口实现类对象[7]。这种方式无须事先定义很多代理类,而是将不同接口的逻辑统一放置在InvocationHandler接口实现类对象的invoke()函数中进行动态区分。

1.4 声明式编程

声明式编程是用一种编程范式,表示逻辑运算时不需要说明程序的控制流程,只试图通过描述程序应该完成什么而不是怎样完成来降低程序语言表达式或函数产生的副作用[8]。在声明式编程中,程序员关注的是所需结果,而具体实现的步骤和过程则交由计算机来处理。这使得代码更为简洁、直观,且易于理解和维护。

在Java中可以通过注解和动态代理模式的结合实现声明式编程,开发者可以自定义一些普通的Java接口,然后用一些定义好的注解来描述某个接口是做什么的,而不需要实现该接口每一步的具体步骤。

2. 基于动态代理的声明式HTTP客户端框架的设计与实现

针对传统HTTP请求框架的不足,本文提出了一种声明式的HTTP请求框架,命名为Forest,基于Java动态代理模式进行设计和实现。该框架旨在达成以下关键目标:

(1)声明式接的HTTP请求调用。开发者能够通过普通的自定义Java接口方法直接发起HTTP请求,仅须通过注解明确HTTP请求相关信息,如URL、请求头、Query参数、请求体数据等,无须深入处理HTTP请求发送与接收的烦琐过程。

(2)数据格式的自动识别与转换。Forest能够自动识别数据格式,并自动进行序列化和反序列化操作,为开发者提供便捷的数据处理体验。

(3)灵活的HTTP接口扩展。借助拦截器机制,开发者可以便捷地对HTTP接口进行扩展,满足各种自定义需求。

(4)底层HTTP请求框架的灵活切换。Forest允许根据实际需求选择合适的底层HTTP请求框架,以适应不同项目环境要求。

2.1 Forest架构设计

除了开发者通过注解自定义的声明式接口外,Forest的整个框架可分为动态代理、前端和后端三个部分,如图1所示。

(1)动态代理:是Forest框架为自定义声明式接口动态生成的代理实现。在初始化阶段,Forest通过调用Proxy.newProxyInstance()方法创建动态代理实例,作为声明接口的实际对象,供开发者方便调用。同时,该过程还会将接口上的注解转化为HTTP请求所需的具体信息。

(2)前端:作为开发者调用的接口层,实现了Forest的特有功能,对底层的HTTP协议内容进行了高度的抽象和封装,涵盖HTTP请求、HTTP响应、Query参数、请求头等要素。此外,前端部分代码还会解析接口注解中描述的HTTP请求元数据信息,构建请求对象,并执行参数拼接、拦截器调用、数据转换、日志打印等一系列复杂操作,但不进行实际的网络请求发生操作。

(3)后端:作为实际HTTP网络请求发送与接收的底层部分,封装并集成了Httpclient和OkHttp两大传统HTTP请求框架。为了确保后端框架的通用性和可替换性,每一个传统框架的后端实现代码都必须遵循ForestBackend接口。

2.2 Forest原理

Forest框架的工作流程大致可以分为两个主要过程:接口初始化过程、请求发送过程。

(1)接口初始化过程:在此阶段,Forest框架会详尽地读取开发者所定义的声明式接口的元信息以及注解描述内容,以明确接口对应的各项HTTP相关属性。基于这些信息,Forest框架将生成相应的声明式接口的动态代理对象,作为后续操作的入口。

(2)请求发送过程:当开发者需要发起对远端HTTP服务的请求时,将调用这些动态代理对象的方法。Forest框架会根据方法的调用参数,构建相应的请求,将其发送到远端HTTP网络服务,然后等待并接收服务的响应结果,并在收到结果后进行反序列化,最后返回这些处理好的数据。

2.3 Forest接口初始化过程

Forest接口初始化的过程如图2所示。Forest框架使用配置类ForestConfiguration的对象来进行声名式接口的实例化和初始化过程。ForestConfiguration类作为Forest的全局配置类,负责管理并维护包括默认超时时间、最大连接数等在内的全局配置数据。因此,在使用Forest框架时,该类的对象是第一个需要创建的实例。随后,可基于该对象来创建声名式接口的动态代理实例。

这一动态代理的创建过程主要通过调用以下两个方法来完成。

方法1:获取动态代理工厂。创建或返回ForestProxyFactory类对象。该对象会被缓存,若缓存中有则直接返回。

方法2:通过调用动态代理的createInstance()方法,创建具体的动态代理对象。在此方法中,首先会生成一个InterfaceProxyHandler类的对象。该类作为Forest框架提供的代理处理类,实现了JDK中的InvocationHandler接口,因此所有针对声明式方法的调用,最终都将导向该类对象的invoke()方法。

而在该类的实例化过程中也会对声明式接口的所有接口方法进行初始化,其过程如图3所示。

接口方法初始化时,Forest框架通过反射遍历接口中定义的所有Java方法。若方法带Forest注解,则创建ForestMethod对象封装JDK Method对象,并调用initMethod()初始化:读取Forest注解的URL、请求头、请求体等元数据,并保存在ForestMethod对象中。该接口的所有ForestMethod对象初始化完成后,InterfaceProxyHandler对象也完成初始化,使用Proxy的newProxyInstance()创建动态代理对象并缓存,提高运行效率。

2.4 Forest请求发送过程

请求发送过程可分为以下9个步骤。

(1)调用代理方法。通过Forest接口初始化过程后,会得到一个动态代理对象,当该代理的方法被调用时便会触发请求发送的后续步骤。

(2)构建Forest请求。当接口代理方法被调用时,InterfaceProxyHandler的invoke()方法会找到对应的ForestMethod对象,并调用其makeRequest()方法进行Forest请求对象的构建。此过程会去读HTTP元数据,并组装到Forest请求对象的URL、Query参数、请求头等属性中。

(3)执行Forest请求。Forest请求对象实现了框架前端对HTTP请求的抽象封装。构建完成后,通过execute()方法启动请求执行过程,系统在调用后端框架之前会先进行预处理,包括前置拦截器调用和Cookie处理,以扩展Forest功能。预处理后,根据配置选择后端客户端接口,然后再执行后端HTTP框架的请求过程。

(4)创建后端执行器。在进行下一步之前,先要创建后端执行器,因为是通过它来执行实际的网络请求过程的。

如图4所示,HttpBackend接口有HttpclientBackend和OkHttpBackend两个实现类,分别基于Apache Httpclient和OkHttp框架。采用类似抽象工厂模式,HttpBackend是HttpExecutor接口实现类的抽象工厂,通过HttpBackendSelector的select()方法反射获取。这种设计避免了与具体后端框架的耦合,方便切换和扩展。获取HttpBackend实现类后,可调用createExecutor()方法创建后端执行器。

(5)构建后端请求。后端请求指后端HTTP框架中实际执行请求的对象。如Apache Httpclient的HttpclientExecutor创建的是Apache Httpclient请求对象,OkHttpExecutor则创建OkHttp请求对象。

当后端请求对象创建后,Forest框架会自动映射Forest请求属性到后端请求属性上,随后系统准备发起HTTP网络请求。

(6)发送网络请求并等待响应。在这一步骤中,将先前构建的后端请求对象执行同步网络发送操作,此过程将阻塞当前线程,直至远端HTTP服务返回响应或遇到错误、异常等情况,这时后续代码才会继续执行。

(7)构建Forest响应。在接收到远端HTTP服务的响应或发生错误时,当前线程将恢复执行,并会首先创建并初始化一个Forest响应对象,此对象是对后端HTTP框架响应对象的包装。

(8)处理并返回响应数据。构建Forest响应对象后,通过调用其isSuccess()方法来判断请求是否成功。一旦成功,就可以执行后续步骤。

通过响应对象可以获取HTTP响应的数据流。然后将其反序列化为与声明式接口方法的返回类型匹配的数据。一旦反序列化成功,就可以调用后置拦截器,最后返回结果给调用者,从而完成整个流程。

(9)错误处理。若计算机网络或远端服务不稳定,如网络超时、服务错误、参数错误等,请求可能失败。使用Forest的isSuccess()判断请求结果,有错误则调用拦截器或抛出异常。可通过getStatus()和getException()获取响应码和异常信息。

3. Forest框架应用实践

本文提出的Forest框架已经开源,仓库地址:https://github.com/dromara/forest。自2018年起,垂直电商网站野兽派(https://www.thebeastshop.com)使用Forest框架重构支付系统,对接多个支付平台。传统HTTP框架处理复杂且维护困难。Forest框架重构后,支付系统减少重复代码,每个平台仅需创建声明式接口,提升系统可管理性和维护性。拦截器编写签名过程,实现与请求接口解耦,避免与业务代码混淆。此后,支付系统还接入收钱吧支付平台。

通过本框架,野兽派重构支付系统,解耦业务代码与HTTP请求,降低第三方平台对接复杂度。本框架确保HTTP请求调用的有效性、稳定性、简单性和可维护性,同时提升系统可扩展性,为应用系统带来实际效益。

结语

随着企业对微服务架构和第三方服务依赖增加,HTTP请求调用场景变复杂。Forest框架为此提供高效解决方案。相比传统HTTP框架,Forest集成了动态代理层,涉及数据转换、日志打印等功能,性能略有损失。未来,将持续优化框架性能,追求接近底层HTTP框架的请求调用延迟,为开发者提供高效开发环境。同时,将跟进新兴HTTP协议,如Websocket、HTTP/3、QUIC,确保Forest框架持续适配新技术。

参考文献:

[1]张乾.Web应用与OpenAPI对接的适应性框架的研究[D].秦皇岛:燕山大学,2021.

[2]李大鹏.基于Rest风格web服务的研究[J].电子商务,2010(4):63,65.

[3]王超,闾陈莉,吴迪,等.基于HttpClient的Android客户端的设计与实现[J].计算机时代,2014(3):30-32.

[4]李群.基于OkHttp的文件传输设计与实现[J].电子技术与软件工程,2018(13):180-181.

[5]卢增宁.设计模式及其在软件设计中的应用[J].信息与电脑(理论版),2020,32(16):127-129.

[6]钟茂生,王明文.软件设计模式及其使用[J].计算机应用,2002(8):32-35.

[7]卢楠.Java动态代理的研究与应用[J].计算机与网络,2014,40(12):50-52.

[8]郑浩然,肖伟.基于规则引擎的JAVA声明式编程[J].计算机应用与软件,2009,26(12):132-134.

作者简介:龚骏,硕士研究生,高级工程师,319288386@qq.com,研究方向:Java后台架构、微服务、分布式。

猜你喜欢
序列化调用开发者
如何建构序列化阅读教学
甘肃教育(2020年14期)2020-09-11 07:58:36
核电项目物项调用管理的应用研究
LabWindows/CVI下基于ActiveX技术的Excel调用
测控技术(2018年5期)2018-12-09 09:04:46
基于系统调用的恶意软件检测技术研究
Java 反序列化漏洞研究
16%游戏开发者看好VR
CHIP新电脑(2016年3期)2016-03-10 13:06:42
iOS开发者调查
电脑迷(2015年8期)2015-05-30 12:27:10
iOS开发者调查
电脑迷(2015年4期)2015-05-30 05:24:09
作文训练微格化、序列化初探
语文知识(2015年12期)2015-02-28 22:02:15
利用RFC技术实现SAP系统接口通信