记一次Java动态代理实践
導(dǎo)語:在Java生態(tài)中,我們經(jīng)常直接或者間接的用到動態(tài)代理,比如通過動態(tài)代理調(diào)用遠(yuǎn)程服務(wù),再比如通過動態(tài)代理實現(xiàn)解耦。本文結(jié)合京東服務(wù)框架JSF,講述京東使用動態(tài)代理進(jìn)行抽象的一次實踐,以達(dá)到升級數(shù)據(jù)庫訪問層的目的。
劉世杰,京東商城Java高級開發(fā)工程師,一直從服務(wù)端研發(fā)工作,目前主要負(fù)責(zé)京東海外站商品主數(shù)據(jù)基礎(chǔ)服務(wù)。個人喜歡讀源碼,注重細(xì)節(jié),有些許代碼潔癖。對高可用、高性能、高并發(fā)方面的技術(shù)保持持續(xù)關(guān)注。
1. 背景
最近在做數(shù)據(jù)庫(MySQL)方面的升級改造?,F(xiàn)狀是數(shù)據(jù)庫同時被多個應(yīng)用直連,存在了一些問題:
有大量的重復(fù)代碼,維護(hù)成本較高,也不優(yōu)雅;
出現(xiàn)SQL語句質(zhì)量的問題無法很快定位到是哪個應(yīng)用導(dǎo)致的;
數(shù)據(jù)庫調(diào)用方過于分散,不便于統(tǒng)一控制,比如部分業(yè)務(wù)數(shù)據(jù)的讀寫、屏蔽等;
業(yè)務(wù)的發(fā)展,有的表數(shù)據(jù)量已經(jīng)到了一定的規(guī)模,幾百萬到幾千萬不等,數(shù)據(jù)庫存儲拆分是必須要進(jìn)行的事情。
解決問題的方式很簡單,就是把各應(yīng)用中與此業(yè)務(wù)相關(guān)的dao層抽象成為一個單獨應(yīng)用(以下稱為internal-rpc-app),進(jìn)行統(tǒng)一管理。
?
2. 具體實現(xiàn)
具體的業(yè)務(wù)應(yīng)用與internal-rpc-app的內(nèi)部通信使用公司內(nèi)部具有服務(wù)治理功能的RPC框架JSF,JSF是一款穩(wěn)定高效的框架,它對服務(wù)治理的粒度是接口,接口通過Spring做服務(wù)的發(fā)布和調(diào)用配置。每個接口對應(yīng)一個數(shù)據(jù)表的CURD及特殊業(yè)務(wù)。
2.1 標(biāo)準(zhǔn)版本 1.0
2.1.1. 接口注冊申請
注冊接口: com.jd.xx.BizRpcService1
注冊接口: com.jd.xx.BizRpcService2
注冊接口: com.jd.xx.BizRpcService3
......
注冊接口: com.jd.xx.BizRpcServiceN
2.1.2. 服務(wù)提供方配置
<jsf:provider id="rpc1" interface="com.jd.xx.BizRpcService1"/>
<jsf:provider id="rpc2" interface="com.jd.xx.BizRpcService2"/>
<jsf:provider id="rpc3" interface="com.jd.xx.BizRpcService3"/>
......
<jsf:provider id="rpcN" interface="com.jd.xx.BizRpcServiceN"/>
2.1.3. 客戶端調(diào)用方配置
<jsf:consumer id="rpc1" interface="com.jd.xx.BizRpcService1"/>
<jsf:consumer id="rpc2" interface="com.jd.xx.BizRpcService2"/>
<jsf:consumer id="rpc3" interface="com.jd.xx.BizRpcService3"/>
......
<jsf:consumer id="rpcN" interface="com.jd.xx.BizRpcServiceN"/>
2.1.4 小結(jié)
此版本實現(xiàn)了我們的最初的目的,但是有一個不太好的地方,就是配置的工作量太高,前面有說到JSF框架的治理維度是接口。這也意味著,每次新增接口都要提交申請操作,同時要在consumer和provider做相對應(yīng)的配置,幾個還好,如果數(shù)據(jù)表有幾十個上百個,重復(fù)的工作量就很大。同時內(nèi)部接口也不需要做太細(xì)粒度的服務(wù)治理。于是有了第二版,主要目的是簡化大量重復(fù)配置。
?
2.2 優(yōu)化調(diào)用體驗版本2.0
2.2.1 內(nèi)部實現(xiàn) - 客戶端
首先定義調(diào)用API:
BizRpcService1 bizRpcService1 = InternalPrxoyServices.BizRpcService1;
bizRpcService1.method1(param1, param2);
InternalPrxoyServices關(guān)鍵代碼:
BizProxyRpcService1關(guān)鍵代碼:
BaseProxyService關(guān)鍵代碼:
InternalRpcService接口定義:
2.2.2 內(nèi)部實現(xiàn)-服務(wù)端
服務(wù)端實現(xiàn)比較簡單,直接根據(jù)接口傳過來當(dāng)serviceName、methodName、objects即可定位到具體service到方法,直接執(zhí)行即可。
2.2.3 小結(jié)
到這里,我們通過靜態(tài)代理實現(xiàn)了具體的目標(biāo),通過實現(xiàn)具體的接口類。我們不再需要定義過多的配置了,客戶端調(diào)用也變得簡單明了。
那么,結(jié)束了嗎?并沒有,在解決了大量配置的問題的同時,因為要寫大量的代理類,又引入了新的工作量。
2.3 最終版本3.0
在這一版,我們很自然的引入了動態(tài)生成代理。
2.3.1 客戶端具體實現(xiàn)
因為引入了動態(tài)代理,所以要重新改寫InternalPrxoyServices:
ProxyServiceFactory關(guān)鍵代碼:
InternalRpcProxy關(guān)鍵代碼:
?
2.4 小結(jié)
至此,已經(jīng)解決了2.2.3提到的大量創(chuàng)建代理類的問題。當(dāng)然我們還做了很多文章中沒有提及的事情,比如:
-
通過聲明哪些接口可以走動態(tài)代理
-
方法重名、客戶端接口合法性等校驗
-
將method存入緩存
?
3. 總結(jié)
通過建立獨立的應(yīng)用,解決了前面數(shù)據(jù)庫被多應(yīng)用讀寫所產(chǎn)生的問題,通過開發(fā)了統(tǒng)一接口解決了服務(wù)端和客戶端配置過多的問題。代碼經(jīng)過一步步抽象后,最終發(fā)現(xiàn)實現(xiàn)了一個簡單的RPC雛形,只不過通信層是基于公司的JSF框架。這引發(fā)了一些框架方面的思考:
是否應(yīng)該去掉人工審批類似的流程?
是否應(yīng)該允許更靈活的發(fā)布服務(wù)?比如:根據(jù)注解自動掃描發(fā)布服務(wù)、根據(jù)注解自動獲取服務(wù)。
?
這不是技術(shù)問題,而是怎么權(quán)衡的問題。?
總結(jié)
以上是生活随笔為你收集整理的记一次Java动态代理实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kubernetes大集群怎么管?基于监
- 下一篇: 从滴滴出行业务中台实践聊聊如何构建大中台