一種業務耦合的分治方案設計
前言
在業務迭代的過程中,避免不了業務程式碼交叉混排的情況,同一個呼叫時機出現各種不同業務或功能的程式碼。我們需要將呼叫點作為能力抽象出來,從而成為擴充套件點,將原本序列交叉的程式碼進行抽離,回到真正屬於它的歸處。本文聚焦於實現一種Android技術框架,以儘可能小的開發成本,實現混排業務分治的能力。
方案設計
設計原則
基於介面實現對通用能力的抽離,進行耦合拆分。面向介面程式設計,通過一定的抽象,保證業務分治的同時,還可以保證擴充套件性。
-
開閉原則,基於擴充套件點的設計,方便地進行擴充套件,無需修改原類;
-
依賴倒置,面向介面程式設計,不依賴具體的實現類;
-
介面隔離,將功能抽象成多個隔離的介面,降低耦合;
設計方案
-
有A、B、C三個模組,B和C存在相互依賴、相互呼叫的情況,且在A模組中被交叉呼叫。
-
由A抽離擴充套件點能力到 A_Interface,B和C在A中的呼叫,就變成了 A_Interface 的實現類在A中的呼叫了,確保A不依賴B和C的具體實現。
-
將B、C各分成兩層,上層 implement 為業務實現層,程式碼封閉不對外暴露。下層 interface 為介面層,會對外部公開,可以被外部依賴,表示對外提供的介面能力。B和C之間通過相互提供的介面能力呼叫。
-
分層之後,implement 實現層之間的依賴耦合被去除,只會依賴介面層。
-
將介面和實現的對映關係儲存到工具API類中,實現層通過工具API類實現相互呼叫。
Chain框架
框架設計
-
實現類標註自己實現的介面和名字,生成一種索引關係,將索引儲存到API單例類 ChainBlock 中。
-
ChainBlock 通過 interface.class 和 key 唯一索引到一個實現類上,呼叫實現類的介面方法。
編譯期索引生成
將介面類和例項物件的索引關係,通過手動呼叫的方式註冊到 ChainBlock 的單例中,會變得相當繁瑣。如果編譯時可以掃描到所有註解類,通過註解生成索引關係,這樣在執行時就可以直接使用,大大降低了使用成本。
-
有A、B、C三個 module,拆成獨立的庫,分別編譯成aar整合到主工程。
-
在每個 module 中,通過
@Chain
標註需要通過介面公開的實現類上,被註解的類會在子庫編譯成aar時,通過 AnnotationProcessor 和 javapoet 在編譯期被索引到 XChainImpl.class 中,每個子庫都會生成對應的XChainImpl.class。 -
在主工程編譯時,通過自定義的 plugin,通過 transform、asm、javassist,將子庫所有的索引類XChainImpl.class 插樁到指定的 ChainJoint.class 中,實現對索引類的聚合。
-
在執行時,
ChainBlock
工具類可以從 ChainJoint 中獲取所有的索引,Amodule 就可以通過 ChainBlock 獲取 Bmodule 中註解了@Chain
的實現類了。
執行時API實現
-
添加註解 @ChainHost、@Chain、@Block,分別對介面、實現類、方法進行註解,加上註解才能成為對外公開的API能力。
-
編譯時索引,通過介面類、實現類名字,索引到唯一的實現類上。索引在編譯時建立,不佔用執行時效率。
-
執行時呼叫,通過ChainBlock工具類,完成具體實現類的呼叫。
-
詳細API還包括,獲取所有的Chain列表,以及通過動態代理的方式呼叫所有實現類的方法,甚至可以通過一個自定義的協議url調起某個公開實現類的方法。
// 1. 呼叫指定Chain
ChainBlock.instance().obtainChain(IProvider.class, "A").log("hello");
ChainBlock.instance().obtainChain(IProvider.class, "BProvider").log("hi");
// 2. 獲取所有Chain
List<IProvider.class> list = ChainBlock.instance().getChainList(IProvider.class);
// 3. 按優先順序呼叫所有Chain,按方法上@Block註解的priority從高到低呼叫
ChainBlock.instance().priorityListProxy(IProvider.class).log("hello");
// 4. 通過chainblock自定義協議呼叫
ChainBlock.instance().runChainBlock(
"chainblock://iprovider/A/log?text=hello", context);
-
編譯時文件輸出,將所有子庫中添加了註解的介面和實現類在編譯時輸出到文件,用於查閱現有的一些能力。
Generated by @Chain
# interface com.taobao.idlefish.IProvider
> 描述介面的作用
1. A : { annotatedClass:com.taobao.idlefish.AProvider, singleton:false }
1. BProvider : { annotatedClass:com.taobao.idlefish.BProvider, singleton:false }
1. CProvider : { annotatedClass:com.taobao.idlefish.CProvider, singleton:false }
-
基於 Graphviz工具 的視覺化輸出,更直觀看到ChainBlock的整體鏈路。
遇到的問題
-
索引關係覆蓋問題
-
通過 @Chain 註解新增的 name,有可能會重複,導致可能會覆蓋其他Chain的索引,問題一旦出現,只能在執行時發現。
-
解決辦法:在編譯時進行干預,在Plugin的transform裡,掃描所有的jar包,通過URLClassLoader載入XXXChainImpl.class的索引類,將索引關係新增到 Map 裡面,檢查覆蓋情況,發現覆蓋直接終止編譯報錯,將問題在編譯時提前暴露出來。
-
穩定性問題
-
@Chain 註解的實現類有可能會被刪除,刪除之後obtainChain(IProvider.class, "A").log("hello")方法呼叫會遇到空指標問題。
-
解決辦法:通過 obtainChain 返回的不是真正的實現類,返回的是動態代理的 Proxy 物件,代理到真正的實現類上,即使實現類為 null 了,呼叫代理類也不會報錯。
-
編譯時遇到的 broken jar 問題
-
在 Gradle 編譯時執行 Plugin 的時候,出現了broken jar 的錯誤
-
解決辦法:broken jar 出現的原因是讀取了一個未被正常釋放的 jar 包,可以通過./gradlew --stop臨時解決。經過排查之後,發現 URLClassLoader 和 javassist 的 ClassPool 在載入類物件的時候,需要將 jar 包路徑 add 進去,未被正常釋放導致的問題,正確釋放之後解決。
特點總結
-
介面下沉,基於介面的依賴解耦;
-
低侵入式,註解即可;
-
自動元件註冊,無需手動管理;
-
自定義協議,支援跨程序呼叫;
-
自動報表文件輸出,圖形化展示;
-
混淆友好,不需要額外新增混淆規則;
最佳實踐
不建議使用的場景:
-
如果程式碼可以直接呼叫到,沒必要使用Chain。
-
如果程式碼屬於業務無關的通用能力,可以直接下沉到底層庫,沒必要使用Chain。
建議使用的場景:
-
Chain更適合業務能力的抽象,相互隔離的業務子庫之間,有相關程式碼需要複用,可以通過Chain實現。
-
向外提供擴充套件點,通過介面約束擴充套件點的方法,可以使用Chain拿到所有實現類來批量處理,並且新增實現類的時候對擴充套件點是無感知的。比如JSBridge的呼叫分發,實現類可以通過Chain進行聚合分發。
基於Chain的業務改造
-
改造前,在 MainActivity 中各種業務呼叫相互穿插耦合,難以維護。
-
改造後,抽象一個 IWorkflow 介面,將 MainActivity 中的生命週期能力提供出來,各業務方實現這個介面並添加註解,MainActivity 就可以通過 ChainBlock 工具將生命週期分發到各個業務實現上。各個業務模組程式碼可以收斂到一處,進行分治互不影響。
總結
目前Chain的能力在業務拆解過程中發揮了比較重要的作用,實現了各業務程式碼的分治,同時低侵入式的特性也大大降低了改造的成本。Chain可以作為元件化能力的補充,希望可以給大家帶來一些啟發和收穫。
:tangerine:橙子說
閒魚技術聯合大淘寶技術
新春拜年
“虎虎虎”
紙質紅包大派送
掃碼關注”淘系技術“回覆"紅包“即可獲得領取方式
(2月28日18:00截止)
- Flutter富文字編輯器系列文章3——互動篇
- Flutter富文字編輯器系列文章3——互動篇
- 打造Flutter高效能富文字編輯器——渲染篇
- 節日獻禮:Flutter圖片庫重磅開源!
- 節日獻禮:Flutter圖片庫重磅開源!
- 關於閒魚測試資料構造,我有幾條心得
- 關於閒魚測試資料構造,我有幾條心得
- 打造Flutter高效能富文字編輯器——協議篇
- 打造Flutter高效能富文字編輯器——協議篇
- 閒魚前端技術體系的背後——魔魚(良心推薦,從思路到實踐)
- 閒魚如何保障交易鏈路質量
- Flutter 音影片開發的新思路
- 實效性與準確性的背後:多系統資料聚合展示
- Flutter滑動體驗對齊原生-滑動曲線篇
- 閒魚搜尋-成交寬度優化實踐
- 閒魚策略中樞業務擴充套件模組實現
- 閒魚互動玩法標準化建設
- 一條慢SQL引發的改造
- Flutter切面的應用與擴充套件
- 程式設計師如何保持學習成長?