
摘要:隨著網(wǎng)絡技術的發(fā)展,越來越多項目需要調用第三方服務接口來獲取資源或完成某些功能。使用傳統(tǒng)的HTTP客戶端框架對接這些差異極大的API,開發(fā)和維護成本越來越高。本文提出了一種基于動態(tài)代理模式的Java聲明式HTTP網(wǎng)絡請求框架,利用此框架通過調用Java接口發(fā)送HTTP請求的方式,可有效提高開發(fā)效率,降低系統(tǒng)維護成本。同時,可通過自定義攔截器和自定義注解來增強框架的擴展能力。
關鍵字:Java;動態(tài)代理;聲明式HTTP請求框架;Forest框架
引言
隨著網(wǎng)絡技術的發(fā)展,越來越多的項目需要和第三方企業(yè)進行對接,這些服務商提供的接口大多是基于HTTP的OpenAPI[1](open application programming interface),即開放平臺接口。但是,每家公司的API都有或多或少的差異。有的遵循RESTful風格[2],有的不遵循任何HTTP規(guī)范;有的需要SSL雙向認證,有的只需要SSL單向認證;有的以JSON格式進行數(shù)據(jù)傳輸,有的以XML格式進行數(shù)據(jù)傳輸,類似的細節(jié)差異還有很多。
如果使用傳統(tǒng)HTTP客戶端框架處理這些接口對接工作,會要求開發(fā)者從HTTP請求參數(shù)、頭信息、簽名、SSL認證,到數(shù)據(jù)序列化與反序列化都要自行處理,十分煩瑣,并且很容易將HTTP協(xié)議相關的細節(jié)與業(yè)務代碼混在一起,增加了代碼維護難度和接口管理難度。
本文提出的聲明式HTTP請求框架Forest,將HTTP請求代碼分為接口定義和接口調用兩部分,接口定義部分通過Java注解直觀地描述接口方法所對應的HTTP相關內容,接口調用部分則可以像調用普通Java方法一樣調用接口,而不必關心HTTP請求的具體細節(jié)。這樣不但有效解耦HTTP請求代碼和其他Java業(yè)務代碼,還能對開發(fā)者屏蔽所有不同HTTP請求之間的差異,提高了代碼的健壯性和可維護性,并且使得第三方HTTP接口都被集中在相應的Java接口類中,更容易進行管理和維護。同時,F(xiàn)orest框架會自動進行數(shù)據(jù)的序列化和反序列化,簡化了發(fā)送HTTP請求和接收服務端響應數(shù)據(jù)的過程。
1. Java的HTTP請求相關技術與設計模式
在Java編程領域,當涉及HTTP網(wǎng)絡請求的處理時,開發(fā)者擁有多樣的選擇。除了JDK自帶的HttpURLConnection類外,眾多第三方開源庫也提供了強大的支持,其中Apache HttpClient和OkHttp使用尤為廣泛,已成了業(yè)界的佼佼者。這些庫不僅各具特色,而且憑借各自的優(yōu)勢,能夠靈活應對各種HTTP網(wǎng)絡請求場景。
1.1 Httpclient框架
HttpClient是Apache Jakarta Common下的子項目[3],是目前在Java、Android等領域中使用最廣泛的HTTP請求框架之一,其對HTTP協(xié)議標準支持較為寬泛,功能也很豐富,但使用起來比較復雜。
1.2 OkHttp框架
OkHttp是一套處理HTTP網(wǎng)絡請求的依賴庫,由Square公司設計研發(fā)并開源,目前可以在Java和Kotlin中使用[4],支持HTTP/2、SPDY等較新的網(wǎng)絡協(xié)議,但不支持某些非標準的HTTP請求。
1.3 動態(tài)代理模式
1994年,四位作者:Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides提出了設計模式的概念,代理模式是他們提出的23種經(jīng)典設計模式之一[5-6],其核心作用是為其他對象提供一種代理機制,從而實現(xiàn)對目標對象的訪問控制。在某些特定場景下,當調用者無法或不愿直接引用某個對象時,可以借助“代理”這一中介者來實現(xiàn)間接引用。
代理模式主要分為靜態(tài)代理和動態(tài)代理兩種形式。靜態(tài)代理要求在編譯階段就預先定義好代理類,并通過這些代理類間接地調用被代理的接口。然而,當需要代理的接口數(shù)量龐大時,這種方式就需要準備相應數(shù)量的代理類。
動態(tài)代理則是利用Java的反射機制,在運行時動態(tài)地創(chuàng)建并生成指定接口的代理對象,每個代理對象都會一個相關聯(lián)的InvocationHandler接口實現(xiàn)類對象[7]。這種方式無須事先定義很多代理類,而是將不同接口的邏輯統(tǒng)一放置在InvocationHandler接口實現(xiàn)類對象的invoke()函數(shù)中進行動態(tài)區(qū)分。
1.4 聲明式編程
聲明式編程是用一種編程范式,表示邏輯運算時不需要說明程序的控制流程,只試圖通過描述程序應該完成什么而不是怎樣完成來降低程序語言表達式或函數(shù)產(chǎn)生的副作用[8]。在聲明式編程中,程序員關注的是所需結果,而具體實現(xiàn)的步驟和過程則交由計算機來處理。這使得代碼更為簡潔、直觀,且易于理解和維護。
在Java中可以通過注解和動態(tài)代理模式的結合實現(xiàn)聲明式編程,開發(fā)者可以自定義一些普通的Java接口,然后用一些定義好的注解來描述某個接口是做什么的,而不需要實現(xiàn)該接口每一步的具體步驟。
2. 基于動態(tài)代理的聲明式HTTP客戶端框架的設計與實現(xiàn)
針對傳統(tǒng)HTTP請求框架的不足,本文提出了一種聲明式的HTTP請求框架,命名為Forest,基于Java動態(tài)代理模式進行設計和實現(xiàn)。該框架旨在達成以下關鍵目標:
(1)聲明式接的HTTP請求調用。開發(fā)者能夠通過普通的自定義Java接口方法直接發(fā)起HTTP請求,僅須通過注解明確HTTP請求相關信息,如URL、請求頭、Query參數(shù)、請求體數(shù)據(jù)等,無須深入處理HTTP請求發(fā)送與接收的煩瑣過程。
(2)數(shù)據(jù)格式的自動識別與轉換。Forest能夠自動識別數(shù)據(jù)格式,并自動進行序列化和反序列化操作,為開發(fā)者提供便捷的數(shù)據(jù)處理體驗。
(3)靈活的HTTP接口擴展。借助攔截器機制,開發(fā)者可以便捷地對HTTP接口進行擴展,滿足各種自定義需求。
(4)底層HTTP請求框架的靈活切換。Forest允許根據(jù)實際需求選擇合適的底層HTTP請求框架,以適應不同項目環(huán)境要求。
2.1 Forest架構設計
除了開發(fā)者通過注解自定義的聲明式接口外,F(xiàn)orest的整個框架可分為動態(tài)代理、前端和后端三個部分,如圖1所示。
(1)動態(tài)代理:是Forest框架為自定義聲明式接口動態(tài)生成的代理實現(xiàn)。在初始化階段,F(xiàn)orest通過調用Proxy.newProxyInstance()方法創(chuàng)建動態(tài)代理實例,作為聲明接口的實際對象,供開發(fā)者方便調用。同時,該過程還會將接口上的注解轉化為HTTP請求所需的具體信息。
(2)前端:作為開發(fā)者調用的接口層,實現(xiàn)了Forest的特有功能,對底層的HTTP協(xié)議內容進行了高度的抽象和封裝,涵蓋HTTP請求、HTTP響應、Query參數(shù)、請求頭等要素。此外,前端部分代碼還會解析接口注解中描述的HTTP請求元數(shù)據(jù)信息,構建請求對象,并執(zhí)行參數(shù)拼接、攔截器調用、數(shù)據(jù)轉換、日志打印等一系列復雜操作,但不進行實際的網(wǎng)絡請求發(fā)生操作。
(3)后端:作為實際HTTP網(wǎng)絡請求發(fā)送與接收的底層部分,封裝并集成了Httpclient和OkHttp兩大傳統(tǒng)HTTP請求框架。為了確保后端框架的通用性和可替換性,每一個傳統(tǒng)框架的后端實現(xiàn)代碼都必須遵循ForestBackend接口。
2.2 Forest原理
Forest框架的工作流程大致可以分為兩個主要過程:接口初始化過程、請求發(fā)送過程。
(1)接口初始化過程:在此階段,F(xiàn)orest框架會詳盡地讀取開發(fā)者所定義的聲明式接口的元信息以及注解描述內容,以明確接口對應的各項HTTP相關屬性。基于這些信息,F(xiàn)orest框架將生成相應的聲明式接口的動態(tài)代理對象,作為后續(xù)操作的入口。
(2)請求發(fā)送過程:當開發(fā)者需要發(fā)起對遠端HTTP服務的請求時,將調用這些動態(tài)代理對象的方法。Forest框架會根據(jù)方法的調用參數(shù),構建相應的請求,將其發(fā)送到遠端HTTP網(wǎng)絡服務,然后等待并接收服務的響應結果,并在收到結果后進行反序列化,最后返回這些處理好的數(shù)據(jù)。
2.3 Forest接口初始化過程
Forest接口初始化的過程如圖2所示。Forest框架使用配置類ForestConfiguration的對象來進行聲名式接口的實例化和初始化過程。ForestConfiguration類作為Forest的全局配置類,負責管理并維護包括默認超時時間、最大連接數(shù)等在內的全局配置數(shù)據(jù)。因此,在使用Forest框架時,該類的對象是第一個需要創(chuàng)建的實例。隨后,可基于該對象來創(chuàng)建聲名式接口的動態(tài)代理實例。
這一動態(tài)代理的創(chuàng)建過程主要通過調用以下兩個方法來完成。
方法1:獲取動態(tài)代理工廠。創(chuàng)建或返回ForestProxyFactory類對象。該對象會被緩存,若緩存中有則直接返回。
方法2:通過調用動態(tài)代理的createInstance()方法,創(chuàng)建具體的動態(tài)代理對象。在此方法中,首先會生成一個InterfaceProxyHandler類的對象。該類作為Forest框架提供的代理處理類,實現(xiàn)了JDK中的InvocationHandler接口,因此所有針對聲明式方法的調用,最終都將導向該類對象的invoke()方法。
而在該類的實例化過程中也會對聲明式接口的所有接口方法進行初始化,其過程如圖3所示。
接口方法初始化時,F(xiàn)orest框架通過反射遍歷接口中定義的所有Java方法。若方法帶Forest注解,則創(chuàng)建ForestMethod對象封裝JDK Method對象,并調用initMethod()初始化:讀取Forest注解的URL、請求頭、請求體等元數(shù)據(jù),并保存在ForestMethod對象中。該接口的所有ForestMethod對象初始化完成后,InterfaceProxyHandler對象也完成初始化,使用Proxy的newProxyInstance()創(chuàng)建動態(tài)代理對象并緩存,提高運行效率。
2.4 Forest請求發(fā)送過程
請求發(fā)送過程可分為以下9個步驟。
(1)調用代理方法。通過Forest接口初始化過程后,會得到一個動態(tài)代理對象,當該代理的方法被調用時便會觸發(fā)請求發(fā)送的后續(xù)步驟。
(2)構建Forest請求。當接口代理方法被調用時,InterfaceProxyHandler的invoke()方法會找到對應的ForestMethod對象,并調用其makeRequest()方法進行Forest請求對象的構建。此過程會去讀HTTP元數(shù)據(jù),并組裝到Forest請求對象的URL、Query參數(shù)、請求頭等屬性中。
(3)執(zhí)行Forest請求。Forest請求對象實現(xiàn)了框架前端對HTTP請求的抽象封裝。構建完成后,通過execute()方法啟動請求執(zhí)行過程,系統(tǒng)在調用后端框架之前會先進行預處理,包括前置攔截器調用和Cookie處理,以擴展Forest功能。預處理后,根據(jù)配置選擇后端客戶端接口,然后再執(zhí)行后端HTTP框架的請求過程。
(4)創(chuàng)建后端執(zhí)行器。在進行下一步之前,先要創(chuàng)建后端執(zhí)行器,因為是通過它來執(zhí)行實際的網(wǎng)絡請求過程的。
如圖4所示,HttpBackend接口有HttpclientBackend和OkHttpBackend兩個實現(xiàn)類,分別基于Apache Httpclient和OkHttp框架。采用類似抽象工廠模式,HttpBackend是HttpExecutor接口實現(xiàn)類的抽象工廠,通過HttpBackendSelector的select()方法反射獲取。這種設計避免了與具體后端框架的耦合,方便切換和擴展。獲取HttpBackend實現(xiàn)類后,可調用createExecutor()方法創(chuàng)建后端執(zhí)行器。
(5)構建后端請求。后端請求指后端HTTP框架中實際執(zhí)行請求的對象。如Apache Httpclient的HttpclientExecutor創(chuàng)建的是Apache Httpclient請求對象,OkHttpExecutor則創(chuàng)建OkHttp請求對象。
當后端請求對象創(chuàng)建后,F(xiàn)orest框架會自動映射Forest請求屬性到后端請求屬性上,隨后系統(tǒng)準備發(fā)起HTTP網(wǎng)絡請求。
(6)發(fā)送網(wǎng)絡請求并等待響應。在這一步驟中,將先前構建的后端請求對象執(zhí)行同步網(wǎng)絡發(fā)送操作,此過程將阻塞當前線程,直至遠端HTTP服務返回響應或遇到錯誤、異常等情況,這時后續(xù)代碼才會繼續(xù)執(zhí)行。
(7)構建Forest響應。在接收到遠端HTTP服務的響應或發(fā)生錯誤時,當前線程將恢復執(zhí)行,并會首先創(chuàng)建并初始化一個Forest響應對象,此對象是對后端HTTP框架響應對象的包裝。
(8)處理并返回響應數(shù)據(jù)。構建Forest響應對象后,通過調用其isSuccess()方法來判斷請求是否成功。一旦成功,就可以執(zhí)行后續(xù)步驟。
通過響應對象可以獲取HTTP響應的數(shù)據(jù)流。然后將其反序列化為與聲明式接口方法的返回類型匹配的數(shù)據(jù)。一旦反序列化成功,就可以調用后置攔截器,最后返回結果給調用者,從而完成整個流程。
(9)錯誤處理。若計算機網(wǎng)絡或遠端服務不穩(wěn)定,如網(wǎng)絡超時、服務錯誤、參數(shù)錯誤等,請求可能失敗。使用Forest的isSuccess()判斷請求結果,有錯誤則調用攔截器或拋出異常。可通過getStatus()和getException()獲取響應碼和異常信息。
3. Forest框架應用實踐
本文提出的Forest框架已經(jīng)開源,倉庫地址:https://github.com/dromara/forest。自2018年起,垂直電商網(wǎng)站野獸派(https://www.thebeastshop.com)使用Forest框架重構支付系統(tǒng),對接多個支付平臺。傳統(tǒng)HTTP框架處理復雜且維護困難。Forest框架重構后,支付系統(tǒng)減少重復代碼,每個平臺僅需創(chuàng)建聲明式接口,提升系統(tǒng)可管理性和維護性。攔截器編寫簽名過程,實現(xiàn)與請求接口解耦,避免與業(yè)務代碼混淆。此后,支付系統(tǒng)還接入收錢吧支付平臺。
通過本框架,野獸派重構支付系統(tǒng),解耦業(yè)務代碼與HTTP請求,降低第三方平臺對接復雜度。本框架確保HTTP請求調用的有效性、穩(wěn)定性、簡單性和可維護性,同時提升系統(tǒng)可擴展性,為應用系統(tǒng)帶來實際效益。
結語
隨著企業(yè)對微服務架構和第三方服務依賴增加,HTTP請求調用場景變復雜。Forest框架為此提供高效解決方案。相比傳統(tǒng)HTTP框架,F(xiàn)orest集成了動態(tài)代理層,涉及數(shù)據(jù)轉換、日志打印等功能,性能略有損失。未來,將持續(xù)優(yōu)化框架性能,追求接近底層HTTP框架的請求調用延遲,為開發(fā)者提供高效開發(fā)環(huán)境。同時,將跟進新興HTTP協(xié)議,如Websocket、HTTP/3、QUIC,確保Forest框架持續(xù)適配新技術。
參考文獻:
[1]張乾.Web應用與OpenAPI對接的適應性框架的研究[D].秦皇島:燕山大學,2021.
[2]李大鵬.基于Rest風格web服務的研究[J].電子商務,2010(4):63,65.
[3]王超,閭陳莉,吳迪,等.基于HttpClient的Android客戶端的設計與實現(xiàn)[J].計算機時代,2014(3):30-32.
[4]李群.基于OkHttp的文件傳輸設計與實現(xiàn)[J].電子技術與軟件工程,2018(13):180-181.
[5]盧增寧.設計模式及其在軟件設計中的應用[J].信息與電腦(理論版),2020,32(16):127-129.
[6]鐘茂生,王明文.軟件設計模式及其使用[J].計算機應用,2002(8):32-35.
[7]盧楠.Java動態(tài)代理的研究與應用[J].計算機與網(wǎng)絡,2014,40(12):50-52.
[8]鄭浩然,肖偉.基于規(guī)則引擎的JAVA聲明式編程[J].計算機應用與軟件,2009,26(12):132-134.
作者簡介:龔駿,碩士研究生,高級工程師,319288386@qq.com,研究方向:Java后臺架構、微服務、分布式。