Flutter APP AOP调研、集成、应用说明

274 阅读4分钟

Flutter APP AOP调研、集成、应用说明

2671714361628_.pic.jpg

AOP说明

简介

AOP:面向切面编程,是对OOP的补充和完善.AOP可以在预编译方式和运行期间动态植入逻辑,利用横切技术,剖解开封装的对象内部,对同一切面代码进行统一维护. 便于减少系统的重复代码,降低 模块间的耦合度,并有利于未来的可操作性和可维护性.

AOP是相对于横向的关系,假定OOP对象是圆柱体,其中封装的是对象的属性和行为.那么AOP就是一把刀,将圆柱体横向切开,横向深入到切面的内部.找到具有公共逻辑的,与其他模块的核心逻辑纠缠在一起的行为.切开的面就是所谓的"切面".

必要概念

  • Join Point(连接点): 程序执行中的一个精确执行点,例如类中的某个方法。它是一个抽象概念,可以是方法调用前、调用后或抛出异常后等点。

  • Point Cut(切入点): 捕获连接点的结构,用于定义目标方法的调用点。

  • Advice(通知): 在切入点执行的具体代码逻辑。类型包括:

    • 前置通知(Before): 目标方法调用前执行。
    • 后置通知(After): 目标方法完成后执行(包括异常情况)。
    • 后置返回通知(After-returning): 方法成功执行后调用。
    • 后置异常通知(After-throwing): 方法抛出异常后调用。
    • 环绕通知(Around): 包裹目标方法,前后均可执行自定义逻辑。
  • Aspect(方面): 由切入点和通知组成,相当于OOP中的类,表示对象间横向关系。

常见应用场景

  • 权限认证
  • 日志记录
  • 事务处理
  • App UI 自动化测试

Flutter AOP说明

背景

闲鱼团队在2021年开源了 AspectD(Dart 的 AOP 框架),后续停止维护。贝壳团队基于 AspectD 开源了 Beike_AspectD 框架,betterWE 使用的正是此框架。

AspectD 与 Beike_AspectD 的注解说明

@pragma('vm:entry-point')

作用:在 AOT 编译中防止未被引用的代码被丢弃。
示例代码:

dart
复制代码
@pragma('vm:entry-point')
class PointCut {
  @pragma('vm:entry-point')
  PointCut(this.sourceInfos, this.target, this.function, this.stubKey, this.positionalParams, this.namedParams);

  final Map<dynamic, dynamic> sourceInfos; // 源代码信息
  final Object target;                    // 调用目标
  final String function;                  // 方法名
  final String stubKey;                   // 唯一标识
  final List<dynamic> positionalParams;   // 位置参数
  final Map<dynamic, dynamic> namedParams; // 命名参数

  @pragma('vm:entry-point')
  Object proceed() {
    return null; // 调用原始方法的入口点
  }
}
@Aspect

标记需要进行 AOP 操作的类,用于识别和提取逻辑。移除该注解可禁用对应 AOP 逻辑。
示例代码:

@Aspect()
@pragma("vm:entry-point")
class RegularCallDemo {
  @pragma("vm:entry-point")
  static dynamic appInit(PointCut pointcut) {
    print('[betterWE]: Before appInit!');
    dynamic object = pointcut.proceed();
    print('[betterWE]: After appInit!');
    return object;
  }
}
@Call 与 @Execute 的区别
  • @Call: 插入代码到调用处,例如 main 方法调用 say 方法时,插入到 main 方法中。
  • @Execute: 插入代码到方法体执行处,例如插入到 say 方法内。

示例代码:

@Execute("dart:math", "Random", "-next.*", isRegex: true)
@pragma("vm:entry-point")
static dynamic randomNext(PointCut pointcut) {
  dynamic obj = pointcut.proceed();
  print('[betterWE]: randomNext!');
  return obj;
}

集成 Beike_AspectD

要实现Flutter AOP,需修改Flutter和Dart SDK的源码,因此每个版本都需要单独适配。我所介绍的这个版本仅支持Flutter SDK 3.13.7。更多版本的适配将在后续文章中介绍。

1. 替换 Flutter Tools

  1. 下载Beike_AspectD项目

    • 如果尚未下载,请从GitHub克隆Beike_AspectD仓库到您的本地计算机:

      bash
      深色版本
      git clone https://github.com/zhangshuqi/Beike_AspectD.git
      
  2. 应用补丁文件

    • 进入您的Flutter SDK目录(例如...FlutterSDK/3.13.7),然后执行以下命令来应用位于Beike_AspectD项目中的flutter_tools.patch补丁文件。请将Beike_AspectD-project-path替换为Beike_AspectD项目在您计算机上的实际路径:

      cd .../FlutterSDK/3.13.7
      git apply Beike_AspectD-project-path/inner/flutter_tools.patch
      
  3. 清除缓存

    • 为了确保补丁生效,删除flutter_tools的缓存文件:

      rm bin/cache/flutter_tools.stamp
      

注意:以上步骤每台机器只需执行一次。

2. 添加依赖

pubspec.yaml 中添加:

dependencies:
    beike_aspectd:
      git:
        url: https://github.com/zhangshuqi/Beike_AspectD.git
        ref: 3.13.7

3. 添加 aop_config.yaml

在项目根目录(与 pubspec.yaml 同级)创建 aop_config.yaml,内容如下:

flutter_tools_hook:
  - project_name: 'beike_aspectd'
    exec_path: 'bin/starter.snapshot'

4. 修改代码后清理缓存

每次修改 hook_manager 相关代码后需执行 flutter clean,否则修改不会生效。