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