前言
在 Java 的 Web 世界中最住,構(gòu)建Web應(yīng)用最為常見(jiàn)的 dispatcher 框架遍烦,老一點(diǎn)的 Struts 系列狭姨,現(xiàn)在最為流行的 SpringMVC婆硬。它們起初的目標(biāo)都是構(gòu)建基于模板的 web 應(yīng)用程序狠轻,也就是作為一名當(dāng)時(shí)的程序員,前后端都得會(huì)彬犯。
而隨著分布式系統(tǒng) 和 互聯(lián)網(wǎng)的發(fā)展向楼,前后端分離、后端服務(wù)化的趨勢(shì)越來(lái)越明朗谐区,SpringMVC 加入了諸如 @RestController 這樣概念性的 annotation 以 restful 風(fēng)格的 webservice call 來(lái)語(yǔ)義上支持前后分離 和 后端的服務(wù)化湖蜕。前端程序員不強(qiáng)調(diào)必須會(huì)后端,后端程序員也不強(qiáng)調(diào)必須要會(huì)前端卢佣,JAX-RS 不論用來(lái)做 Web 與前端程序員的分離重荠, 或者后端一系列的服務(wù)都是一個(gè)非常好的選擇。
從 51cto 來(lái)的圖來(lái)表述前后端分離:
從百度查來(lái)的后端分布式服務(wù)圖:
1虚茶、對(duì)應(yīng)后端概念
相信資歷比較老一些的研發(fā)戈鲁,或多或少得接觸過(guò) SOA(Service Oriented Architecture)、ESB(Enterprise Service Bus)嘹叫、RPC(Remote Procedure Call), 還有最近的微服務(wù) 這些概念即是為構(gòu)建復(fù)雜的分布式系統(tǒng)而生婆殿,為后端服務(wù)化提供一套套解決方案的思路。
ESB 企業(yè)資源總線例子圖(百度來(lái)的與作者理解一致的圖罩扇,作者很懶):
2婆芦、概念的 Implementations
這些思路的出現(xiàn),也有著對(duì)應(yīng)的 java 框架進(jìn)化著 ——— 從遙遠(yuǎn)過(guò)去的程序員制定規(guī)則到 SOAP 標(biāo)準(zhǔn)的出現(xiàn)到 Sun 建立的 Restful webservice 標(biāo)準(zhǔn) — JAX-RS喂饥, 再到后來(lái)基于 Netty 的一系列 RPC 框架消约, 誕生了非常非常多優(yōu)秀的 Java 框架,如下面舉一些我的程序職業(yè)中見(jiàn)過(guò)得一些例子员帮。
WebService的SOAP標(biāo)準(zhǔn)系列: Apache Axis2或粮、Codehaus XFire、Apache CXF
Rest 的 WebService 的 JAX-RS 系列: RestEasy捞高、Jersey氯材、CXF
基于 Netty 的 RPC 系列: Finagle渣锦、JProtobuf(我們點(diǎn)融內(nèi)部服務(wù)項(xiàng)目在使用這個(gè)RPC框架)、Thrift氢哮、apache avro
它們讓我們從使用菜刀的冷兵器戰(zhàn)場(chǎng)到使用AK47的熱兵器時(shí)代袋毙。
3、派別模式的優(yōu)點(diǎn)和區(qū)別
在我的 java 世界中有兩個(gè)派別:第一派即 j2ee 系列冗尤,走的是標(biāo)準(zhǔn)化道路听盖,先有標(biāo)準(zhǔn)的定義,再由具體廠商去實(shí)現(xiàn)生闲,再慢慢地進(jìn)化標(biāo)準(zhǔn); 另一派走得比較敏捷一些(Spring 系列), 反設(shè)計(jì)模式的特性媳溺,使得這一派如魚(yú)得水;它們共同構(gòu)成了 java web 的世界碍讯,模式不同但又相互交融悬蔽。
j2ee、spring 類(lèi)似的對(duì)比圖:
講了這么多捉兴,我們進(jìn)入我將要介紹的正題——JAX-RS蝎困, 它屬于 Restful 系列的標(biāo)準(zhǔn)、是 j2ee 系列中的產(chǎn)物之一倍啥。
那么何為標(biāo)準(zhǔn)呢禾乘?標(biāo)準(zhǔn)就像你我寫(xiě)程序時(shí)定義的 interface,我拿著接口使用其中一個(gè)實(shí)現(xiàn)虽缕,即可實(shí)現(xiàn)系統(tǒng)的交互; 標(biāo)準(zhǔn)始藕,也如同攻城射擊獅畫(huà)攻城圖紙使用了神器-UML (統(tǒng)一建模語(yǔ)言), 畫(huà)出來(lái)的東西氮趋,程序員就能夠直接懂里面發(fā)生了什么, 大大減少非標(biāo)準(zhǔn)路子造成的程序員之間的溝通問(wèn)題; 是標(biāo)準(zhǔn) 也就提供了通過(guò)編程序使程序理解程序伍派、自動(dòng)生成代碼的可能性。
JAX-RS 是一種標(biāo)準(zhǔn)也即擁有了這些優(yōu)點(diǎn)剩胁,看了我的后面的代碼诉植,你會(huì)發(fā)現(xiàn)后端程序員可以這樣構(gòu)建 Restful 的 Webservice 服務(wù) 和 客戶端。
下面的代碼中我將介紹一些 JAX-RS 標(biāo)準(zhǔn)能為我們帶來(lái)的好處昵观,如:
自動(dòng)生成可與服務(wù)器交互的客戶端
在客戶端晾腔、服務(wù)端埋點(diǎn):
—— Track可能的性能問(wèn)題
—— 傳遞一些作為上游服務(wù)器擁有的session類(lèi)參數(shù)給無(wú)狀態(tài)的服務(wù)端
—— 其他, 如 配合配置服務(wù)器做 服務(wù)注冊(cè)與發(fā)現(xiàn); 由調(diào)用方傳入 transactionId保證事務(wù)最終一致; 使用 token(如 jwt) 來(lái)獲得 api 的訪問(wèn)權(quán)限 etc啊犬。
4灼擂、Talk is cheap, show me the code.
為了避免篇幅過(guò)于冗長(zhǎng),代碼中僅提供 “四”中明確提出的 3個(gè)好處的實(shí)現(xiàn)觉至。
1)我們既然是 In action缤至, 我不解釋太多標(biāo)準(zhǔn)細(xì)節(jié)的東西,讀者可以自行通過(guò)附件1提供的鏈接,去查詢標(biāo)準(zhǔn)相關(guān)的細(xì)節(jié)领斥,下面是兩張 JAX-RS 的 Lifecycle(具體請(qǐng)參見(jiàn)附件 spec 鏈接)的圖片,讀者可以先嘗試?yán)斫庖幌聢D片 以幫助理解程序沃暗。
Server-side :
Client-side:
2)將附錄2中的源代碼下載下來(lái)月洛,用 intellij 或 eclipse 以 gradle 項(xiàng)目形式導(dǎo)入. 該項(xiàng)目的 JAX-RS 標(biāo)準(zhǔn)由 cxf 實(shí)現(xiàn),與 Springboot 進(jìn)行集成孽锥,在將項(xiàng)目導(dǎo)入后找到 類(lèi) Application嚼黔, 右鍵 debug run,即可將該 springboot 的服務(wù)器端啟動(dòng)惜辑。
3)服務(wù)端啟動(dòng)后唬涧,http 服務(wù)即被放在了 http://localhost:8080, 可訪問(wèn) http://localhost:8080/ws 測(cè)試 cxf servlet dispatcher 是否啟動(dòng)成功。此時(shí)找到 類(lèi) ClientMainDemo盛撑,這個(gè)類(lèi)既是我們生成的客戶端 它是業(yè)務(wù)邏輯碎节、性能監(jiān)控、傳遞一些 session 信息給 stateless 服務(wù)的使用 main 方法的測(cè)試抵卫。我們?cè)?“2” 中的服務(wù)啟動(dòng)好之后狮荔,直接 debug 執(zhí)行 ClientMainDemo 中的 main 方法:
具體代碼詳解我們下一步再講(其實(shí)我不想講解代碼,代碼即是文檔介粘,還煩請(qǐng)各位有興趣的同學(xué)下載代碼來(lái)看殖氏,代碼并不復(fù)雜)我們可以看到如下的日志:
case a.
第一個(gè) request 在服務(wù)端可以看到日志:
在客戶端看到日志:
我們可以看到,服務(wù)器發(fā)現(xiàn)了一個(gè) request姻采,并且記錄了它被 call 了一次(我在代碼中加的日志)雅采,與此同時(shí) 在客戶端我們看到該 request 發(fā)起和結(jié)束的日志, 可以統(tǒng)計(jì)到 request 的狀態(tài)慨亲、耗時(shí)等信息, 在客戶端的track埋點(diǎn)功能 done婚瓜。
case b.
是 DEMO 中的第三個(gè) request, 我們將一個(gè) ThreadLocal 中的變量設(shè)置好巡雨, 并使用 jaxrs 的 provider 進(jìn)行攔截并將該值讀取出來(lái)放進(jìn) http request 的 header闰渔。
我們可以在客戶端看到日志:
服務(wù)端看到日志:
4)執(zhí)行步驟“3”之后,我們發(fā)現(xiàn)我們需要的三個(gè)功能都已經(jīng)實(shí)現(xiàn)了, 我們?cè)賮?lái)具體看一看代碼是怎么做的(具體的框架搭建請(qǐng)參考源碼):
1铐望、自動(dòng)生成可與服務(wù)器交互的客戶端(該客戶端需要 publish 成 jar 包到 repo 中冈涧,給集成方使用) CXF 框架提供了一個(gè)叫做 JAXRSClientFactory 的類(lèi), 可以根據(jù) endpoint + 接口 + jaxrs providers 的方式來(lái)生成客戶端接口, 詳細(xì)代碼參見(jiàn)JaxrsExampleFacade
2正蛙、埋點(diǎn) track 客戶端執(zhí)行性能(服務(wù)端我沒(méi)做)督弓, 讀者可以自己做我自定義了一個(gè)類(lèi) 叫做 UUIDHeaderClientRequestFilter,該類(lèi)實(shí)現(xiàn)了 jaxrs 標(biāo)準(zhǔn)中 client 端的兩個(gè) filter 接口乒验, 并且我將其放進(jìn)了 client 的生命周期愚隧,即可用于生成每個(gè) request 的 uuid 并且將其性能 track 起來(lái),代碼如下:
3锻全、傳遞一些作為上游服務(wù)器擁有的 session 類(lèi)參數(shù)給無(wú)狀態(tài)的服務(wù)端與 UUIDHeaderClientRequestFilter 一樣狂塘, ?實(shí)現(xiàn)一個(gè)叫做 OperatorHeaderClientRequestFilter 的 jaxrs provider (多個(gè) provider 之后請(qǐng)讀者注意控制它們的 Priority)录煤,該 Filter 會(huì)從 ThreadLocal 中讀取變量值,放進(jìn) requestheader荞胡,具體代碼如下:
與此同時(shí)在服務(wù)器端也實(shí)現(xiàn)一個(gè)叫做 OperatorFilter 的類(lèi)妈踊, 作為 jaxrs 的 provider 實(shí)現(xiàn) ContainerRequestFilter 和 ContainerResponseFilter, 將 http header 中的值讀出來(lái)并放到 ThreadLocal 中泪漂, 在 request 結(jié)束時(shí)清空 threadlocal廊营,詳細(xì)代碼如下:
希望這篇文章在您的服務(wù)化之路上有一定思路上的幫助。
附錄:
jax-rs spec下載地址:https://jcp.org/aboutJava/communityprocess/final/jsr339/index.html
本文源碼地址:https://github.com/Agileaq/jax-rs-example.git
Uniauth的讀服務(wù)提供給了點(diǎn)融網(wǎng)各個(gè)集成系統(tǒng)萝勤,其服務(wù)提供的war包 使用了這一套解決方案并且代碼已經(jīng)開(kāi)源露筒,源碼: https://github.com/dianrong/UniAuth
cxf jaxrs文檔: http://cxf.apache.org/docs/jax-rs.html
有任何疑問(wèn), 都?xì)g迎直接與我聯(lián)系并交流經(jīng)驗(yàn): 286999915@qq.com , ?微信號(hào) ?Arc_Qian
本文作者:錢(qián)晟龍 Arc_Qian(點(diǎn)融黑幫)敌卓,現(xiàn)任點(diǎn)融網(wǎng)架構(gòu)組產(chǎn)品研發(fā)工程師慎式,主要任務(wù)是思考并嘗試解決各類(lèi)點(diǎn)融網(wǎng)邁出第一公里之后遇到的現(xiàn)實(shí)問(wèn)題。