(鴻蒙)應用架構-切面編程(AOP)

1.概述

切面編程(AOP)是一種通過預編譯方式和運行期間動態(tài)代理實現(xiàn)程序功能的統(tǒng)一維護的技術。核心思想是將程序的關注點與源代碼進行分離,通過在程序中插入自己的代碼來實現(xiàn)切入點,從而實現(xiàn)對業(yè)務邏輯代碼的隔離,降低它們之間的耦合度,提高程序的可維護性和可重用性,同時提高了開發(fā)的效率。

HarmonyOS主要通過插樁機制來實現(xiàn)切面編程,并提供了Aspect類,包括addBefore、addAfter和replace接口。

2.AOP與傳統(tǒng)方案對比

特性 鴻蒙AOP 傳統(tǒng)方案
代碼侵入性 低(無需修改原代碼,只需添加橫切代碼) 高(橫切代碼分散各處)
可維護性 高(橫切代碼集中管控) 低(橫切代碼需多處修改)
可復用性 高(100%復用) 低(復制粘貼)
可讀性 高(橫切代碼集中管理,業(yè)務邏輯純凈) 低(橫切代碼分散各處,代碼臃腫,冗余)
動態(tài)調(diào)控 高(運行時啟用/禁用切面) 低(需要修改代碼,重新編譯)

3.接口

addBefore

  • 在指定的對象的原方法執(zhí)行前插入一個函數(shù)。
  • 原方法執(zhí)行前,先執(zhí)行插入的函數(shù)邏輯,再執(zhí)行指定類對象的原方法。
?適用場景:參數(shù)校驗、日志記錄、性能統(tǒng)計等。
 /**
   * 在指定的類對象的原方法執(zhí)行前插入一個函數(shù)
   * @param targetClass     指定的類對象
   * @param methodName      指定的方法名,不支持read-only方法。
   * @param isStatic        指定的原方法是否為靜態(tài)方法。true表示靜態(tài)方法,false表示實例方法。
   * @param before          要插入的函數(shù)對象
   *                          如果:函數(shù)有參數(shù),則第一個參數(shù)是this對象
   *                                  (若isStatic為true,則為類對象即targetClass;
   *                                   若isStatic為false,則為調(diào)用方法的實例對象),
   *                                   其余參數(shù)是原方法參數(shù),函數(shù)也可以無參數(shù)。
   */
   static addBefore(
    targetClass: Object,      
    methodName: string,       
    isStatic: boolean,        
    before: Function
   ): void;    

   
   /**
   * befor函數(shù)
   * @param instance     isStatic為true  = targetClass     
   *                     isStatic為false = Object
   * @param ...args      插入方法的參數(shù)
   */
   before: (
    instance: Object,
    ...args: ESObject[]
   ) => {

   };

addAfter

  • 在指定的類方法執(zhí)行后插入一個函數(shù)。最終返回值是插入函數(shù)執(zhí)行后的返回值。
  • 原方法執(zhí)行后,執(zhí)行插入的函數(shù)邏輯,返回插入函數(shù)的返回值。
?適用場景:方法執(zhí)行監(jiān)控和統(tǒng)計、保存原數(shù)據(jù)。
  /**
   * 在指定的類對象的原方法執(zhí)行后插入一個函數(shù),最終返回值是插入函數(shù)執(zhí)行后的返回值
   * @param targetClass     指定的類對象
   * @param methodName      指定的方法名,不支持read-only方法。
   * @param isStatic        指定的原方法是否為靜態(tài)方法。true表示靜態(tài)方法,false表示實例方法。
   * @param after           要插入的函數(shù)對象
   *                          如果:函數(shù)有參數(shù),則第一個參數(shù)是this對象
   *                                  (若isStatic為true,則為類對象即targetClass;
   *                                   若isStatic為false,則為調(diào)用方法的實例對象),
   *                                   第二個參數(shù)是原方法的返回值(如果原方法沒有返回值,則為undefined),
   *                                   其余參數(shù)是原方法參數(shù),函數(shù)也可以無參數(shù)。
   */
  static addAfter(
    targetClass: Object,
    methodName: string,
    isStatic: boolean,
    after: Function
  ): void;


  /**
   * after函數(shù)
   * @param instance     isStatic為true  = targetClass
   *                     isStatic為false = Object
   * @param returnValue  原方法的返回值,如果沒有則為undefined
   * @param ...args      插入方法的參數(shù)
   */
  after: (
    instance: Object,
    returnValue: any,
    ...args: ESObject[]
  ) => {

  };

replace

  • 將指定的類的方法的替換為另一個函數(shù)。
  • 調(diào)用類的方法執(zhí)行時,只會執(zhí)行替換后的函數(shù)邏輯。最終返回值為替換的函數(shù)執(zhí)行完畢的返回值。
?適用場景:方法邏輯替換。
 /**
   * 替換指定類的方法,替換后,原方法將不再執(zhí)行,將執(zhí)行替換后的函數(shù),并且返回替換后的方法返回值
   * @param targetClass     指定的類對象
   * @param methodName      指定的方法名,不支持read-only方法。
   * @param isStatic        指定的原方法是否為靜態(tài)方法。true表示靜態(tài)方法,false表示實例方法。
   * @param instead         替換原方法的函數(shù)
   *                          如果:函數(shù)有參數(shù),則第一個參數(shù)是this對象
   *                                  (若isStatic為true,則為類對象即targetClass;
   *                                   若isStatic為false,則為調(diào)用方法的實例對象),
   *                                   其余參數(shù)是原方法參數(shù),函數(shù)也可以無參數(shù)。
   */
  static replace(
    targetClass: Object,
    methodName: string,
    isStatic: boolean,
    instead: Function
  ): void;


  /**
   * instead函數(shù)
   * @param instance     isStatic為true  = targetClass
   *                     isStatic為false = Object
   * @param ...args      替換方法的參數(shù)
   */
  instead: (
    instance: Object,
    ...args: ESObject[]
  ) => {

  };

4.使用場景

場景1:方法參數(shù)校驗
場景2:統(tǒng)計方法執(zhí)行次數(shù)、時間
場景3:校驗方法返回值
場景4:替換方法實現(xiàn)

5.使用注意事項

  1. 目標類需要導入,沒有導出的場景,可以通過實例的constructor屬性獲取目標類。
  2. 目標方法名不能被混淆。
  3. 對父類作為目標類插樁會影響所有子類;對子類作為目標類插樁不會影響父類(無論方法是否是繼承自父類的),但是會影響子類的所有子類。
  4. 接口的第四個參數(shù)是回調(diào)函數(shù),回調(diào)函數(shù)中第一個參數(shù)是執(zhí)行方法調(diào)用的this對象。如果通過這個調(diào)用原方法,并且沒有退出機制,容易造成無限遞歸調(diào)用。如果需要調(diào)用原方法,需要在接口調(diào)用前將原方法存儲起來。不推薦的用法參考如下示例。
    • 錯誤示例
    class Test {
      foo() {}
    }
    util.Aspect.addBefore(Test, 'foo', false, (instance: Test) => {
      // 無限遞歸
      instance.foo();
    });
    
    new Test().foo();
    
    • 正確示例
    class Test {
      foo() {}
    }
    // 將原方法實現(xiàn)先保存起來
    let originalFoo = new Test().foo;
    util.Aspect.addBefore(Test, 'foo', false, (instance: Test) => {
       // 如果原方法沒有使用this,則可以直接調(diào)用原方法originalFoo();
       // 如果原方法中使用了this,應該使用bind綁定instance,但是會有編譯warningoriginalFoo.bind(instance);
    });
    
  5. 不推薦對struct的方法插樁/替換實現(xiàn)。
    @Component
    struct Index {
      foo(){}
      build(){};
    }
    
    util.Aspect.addBefore(Index, 'foo', false, ...);
    util.Aspect.replace(Index, 'build', false, ...);
    
  6. 接口不限制對系統(tǒng)提供的類方法進行插樁。只要類和方法在運行時是實際存在的對象,并且方法的屬性描述符的writable字段為true,就可以使用對應接口進行插樁和替換。

說明
如果類方法的屬性描述符的writable字段為false,比如凍結(freeze) 的場景, 則不能調(diào)用接口操作這個類方法。
方法的屬性描述符的writable字段默認為true。

  1. 使用Aspect類接口進行插樁,對AoT和JIT編譯后的性能沒有明顯影響。
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容