重磅開源|AOP for Flutter開發利器——AspectD

問題背景

隨著Flutter這一框架的快速發展,有越來越多的業務開始使用Flutter來重構或新建其產品。但在我們的實踐過程中發現,一方面Flutter開發效率高,性能優異,跨平臺表現好,另一方面Flutter也面臨著插件,基礎能力,底層框架缺失或者不完善等問題。

舉個栗子,我們在實現一個自動化錄制回放的過程中發現,需要去修改Flutter框架(Dart層面)的代碼才能夠滿足要求,這就會有了對框架的侵入性。要解決這種侵入性的問題,更好地減少迭代過程中的維護成本,我們考慮的首要方案即面向切面編程。

那么如何解決AOP for Flutter這個問題呢?本文將重點介紹一個閑魚技術團隊開發的針對Dart的AOP編程框架AspectD。

AspectD:面向Dart的AOP框架

AOP能力究竟是運行時還是編譯時支持依賴于語言本身的特點。舉例來說在iOS中,Objective C本身提供了強大的運行時和動態性使得運行期AOP簡單易用。在Android下,Java語言的特點不僅可以實現類似AspectJ這樣的基于字節碼修改的編譯期靜態代理,也可以實現Spring AOP這樣的基于運行時增強的運行期動態代理。 那么Dart呢?一來Dart的反射支持很弱,只支持了檢查(Introspection),不支持修改(Modification);其次Flutter為了包大小,健壯性等的原因禁止了反射。

因此,我們設計實現了基于編譯期修改的AOP方案AspectD。

設計詳圖

重磅開源|AOP for Flutter開發利器——AspectD

典型的AOP場景

下列AspectD代碼說明了一個典型的AOP使用場景:

面向開發者API設計

PointCut的設計

PointCut需要完備表征以怎么樣的方式(Call/Execute等),向哪個Library,哪個類(Library Method的時候此項為空),哪個方法來添加AOP邏輯。 PointCut的數據結構:

其中包含了源代碼信息(如庫名,文件名,行號等),方法調用對象,函數名,參數信息等。 請注意這里的 @pragma('vm:entry-point') 注解,其核心邏輯在于Tree-Shaking。在AOT(ahead of time)編譯下,如果不能被應用主入口(main)最終可能調到,那么將被視為無用代碼而丟棄。AOP代碼因為其注入邏輯的無侵入性,顯然是不會被main調到的,因此需要此注解告訴編譯器不要丟棄這段邏輯。 此處的proceed方法,類似AspectJ中的ProceedingJoinPoint.proceed()方法,調用pointcut.proceed()方法即可實現對原始邏輯的調用。原始定義中的proceed方法體只是個空殼,其內容將會被在運行時動態生成。

Advice的設計

此處的 @pragma("vm:entry-point") 效果同a中所述,pointCut對象作為參數傳入AOP方法,使開發者可以獲得源代碼調用信息的相關信息,實現自身邏輯或者是通過pointcut.proceed()調用原始邏輯。

Aspect的設計

Aspect的注解可以使得ExecuteDemo這樣的AOP實現類被方便地識別和提取,也可以起到開關的作用,即如果希望禁掉此段AOP邏輯,移除@Aspect注解即可。

AOP代碼的編譯

包含原始工程的main入口

從上文可以看到,aop.dart引入  import'package:example/main.dart'as app; ,這使得編譯aop.dart時可包含整個example工程的所有代碼。

Debug模式下的編譯

在aop.dart中引入  import'aop_impl.dart'; 這使得aop_impl.dart中內容即便不被aop.dart顯式依賴,也可以在Debug模式下被編譯進去。

Release模式下的編譯

在AOT編譯(Release模式下),Tree-Shaking邏輯使得當aop_impl.dart中的內容沒有被aop中main調用時,其內容將不會編譯到dill中。通過添加  @pragma("vm:entry-point") 可以避免其影響。

當我們用AspectD寫出AOP代碼,透過編譯aop.dart生成中間產物,使得dill中既包含了原始項目代碼,也包含了AOP代碼后,則需要考慮如何對其修改。在AspectJ中,修改是通過對Class文件進行操作實現的,在AspectD中,我們則對dill文件進行操作。

Dill操作

dill文件,又稱為Dart Intermediate Language,是Dart語言編譯中的一個概念,無論是Script Snapshot還是AOT編譯,都需要dill作為中間產物。

Dill的結構

我們可以通過dart sdk中的vm package提供的dump_kernel.dart打印出dill的內部結構

重磅開源|AOP for Flutter開發利器——AspectD

Dill變換

dart提供了一種Kernel to Kernel Transform的方式,可以通過對dill文件的遞歸式AST遍歷,實現對dill的變換。

基于開發者編寫的AspectD注解,AspectD的變換部分可以提取出是哪些庫/類/方法需要添加怎樣的AOP代碼,再在AST遞歸的過程中通過對目標類的操作,實現Call/Execute這樣的功能。

一個典型的Transform部分邏輯如下所示 :

通過對于dill中AST對象的遍歷(此處的visitMethodInvocation函數),結合開發者書寫的AspectD注解(此處的 aspectdInfoMap 和aspectdItemInfo),可以對原始的AST對象(此處methodInvocation)進行變換,從而改變原始的代碼邏輯,即Transform過程。

AspectD支持的語法

不同于AspectJ中提供的Before/Around/After三種預發,在AspectD中,只有一種統一的抽象即Around。 從是否修改原始方法內部而言,有Call和Execute兩種,前者的PointCut是調用點,后者的PointCut則是執行點。

Call

Execute

Inject

僅支持Call和Execute,對于Flutter(Dart)而言顯然很是單薄。一方面Flutter禁止了反射,退一步講,即便Flutter開啟了反射支持,依然很弱,并不能滿足需求。 舉個典型的場景,如果需要注入的dart代碼里,x.dart文件的類y定義了一個私有方法m或者成員變量p,那么在aop_impl.dart中是沒有辦法對其訪問的,更不用說多個連續的私有變量屬性獲得。另一方面,僅僅對方法整體進行操作可能是不夠的,我們可能需要在方法的中間插入處理邏輯。 為了解決這一問題,AspectD設計了一種語法Inject,參見下面的例子: flutter庫中包含了一下這段手勢相關代碼:

如果我們想要在onTapCancel之后添加一段對于instance和context的處理邏輯,Call和Execute是不可行的,而使用Inject后,只需要簡單的幾句即可解決

通過上述的處理邏輯,經過編譯構建后的dill中的GestureDetector.build方法如下所示: 重磅開源|AOP for Flutter開發利器——AspectD 此外,Inject的輸入參數相對于Call/Execute而言,多了一個lineNum的命名參數,可用于指定插入邏輯的具體行號。

構建流程支持

雖然我們可以通過編譯aop.dart達到同時編譯原始工程代碼和AspectD代碼到dill文件,再通過Transform實現dill層次的變換實現AOP,但標準的flutter構建(即flutter tools)并不支持這個過程,所以還是需要對構建過程做細微修改。 在AspectJ中,這一過程是由非標準Java編譯器的Ajc來實現的。在AspectD中,通過對flutter tools打上應用Patch,可以實現對于AspectD的支持

實戰與思考

基于AspectD,我們在實踐中成功地移除了所有對于Flutter框架的侵入性代碼,實現了同有侵入性代碼同樣的功能,支撐上百個腳本的錄制回放與自動化回歸穩定可靠運行。

從AspectD的角度看,Call/Execute可以幫助我們便捷實現諸如性能埋點(關鍵方法的調用時長),日志增強(獲取某個方法具體是在什么地方被調用到的詳細信息),Doom錄制回放(如隨機數序列的生成記錄與回放)等功能。Inject語法則更為強大,可以通過類似源代碼諸如的方式,實現邏輯的自由注入,可以支持諸如App錄制與自動化回歸(如用戶觸摸事件的錄制與回放)等復雜場景。

進一步來說,AspectD的原理基于Dill變換,有了Dill操作這一利器,開發者可以自由地對Dart編譯產物進行操作,而且這種變換面向的是近乎源代碼級別的AST對象,不僅強大而且可靠。無論是做一些邏輯替換,還是是Json<–>模型轉換等,都提供了一種新的視角與可能。

寫在最后

AspectD作為閑魚技術團隊新開發的面向Flutter的AOP框架,已經可以支持主流的AOP場景并在Github開源,歡迎使用。
Aspectd for Flutter: https://github.com/alibaba-flutter/aspectd
如果你在使用過程中,有任何問題或者建議,歡迎提issue: https://github.com/alibaba-flutter/aspectd/issues 或者PR: https://github.com/alibaba-flutter/aspectd/pulls

原文 

http://mp.weixin.qq.com/s?__biz=MzU4MDUxOTI5NA==&mid=2247484555&idx=1&sn=b0551bcdbbbfb8a5f1a52ca227435984

本站部分文章源于互聯網,本著傳播知識、有益學習和研究的目的進行的轉載,為網友免費提供。如有著作權人或出版方提出異議,本站將立即刪除。如果您對文章轉載有任何疑問請告之我們,以便我們及時糾正。

PS:推薦一個微信公眾號: askHarries 或者qq群:474807195,里面會分享一些資深架構師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發、高性能、分布式、微服務架構的原理,JVM性能優化這些成為架構師必備的知識體系。還能領取免費的學習資源,目前受益良多

轉載請注明原文出處:Harries Blog? » 重磅開源|AOP for Flutter開發利器——AspectD

贊 (0)
分享到:更多 ()

評論 0

  • 昵稱 (必填)
  • 郵箱 (必填)
  • 網址
2013平特肖公式