Flutter AOP框架aspectd入门指引

0x00 前言

什么是aspectd?aspectd是闲鱼针对dart的AOP开源框架。https://github.com/alibaba-flutter/aspectd.git
阅读本文你将得到什么?

  1. 掌握aspectd的环境搭建,并如何在本地成功运行aspectd的demo
  2. 掌握有关aop的基础概念
  3. 了解aspectd的基础用法和原理

0x01 准备

1.1 开发环境

aspectd的环境搭建需要flutter源码、aspectd源码和dart源码,并需要在系统中设置相应的全局环境变量。

1.1.1 flutter环境

下载flutter源码:

git clone https://github.com/flutter/flutter.git

1.1.2 aspectd下载

下载aspectd源码:

git clone https://github.com/alibaba-flutter/aspectd.git

1.1.3 环境变量

配置flutter镜像、本地flutter源码地址、flutter bin目录、dart bin目录:

export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
export PATH_TO_FLUTTER_GIT_DIRECTORY=/Users/Ivonhoe/Flutter/flutter
export PATH=$PATH_TO_FLUTTER_GIT_DIRECTORY/bin:$PATH
export PATH=$PATH_TO_FLUTTER_GIT_DIRECTORY/bin/cache/dart-sdk/bin:$PATH

1.2 安装aspectd

aspectd需要
1.切换到flutter的git目录:
cd ${path-for-git-flutter}
2.将aspectd源码中的git patch文件合并到flutter源码工程中,合并git patch:
git apply --3way ~/Github/aspectd/0001-aspectd.patch
3.删除原有的的flutter编译工具:
rm bin/cache/flutter_tools.stamp
4.重新构建新的flutter编译工具:
flutter doctor -v

1.3 运行

到aspectd源码目录的example目录下执行:
flutter run --debug --verbose
如果你能一次运行成功并aspectd生效,请直接跳转到第二章!

1.4 aspectd编译不过或demo没有效果

编译不过或运行demo没有打印出想要的日志是aspectd使用时最常见的问题。aspectd的基本原理实际上是使用了dart对虚拟语法树操作的api,通过对flutter dill文件进行虚拟语法树遍历,完成对dill文件的转换,进而实现对dart的切面操作。所以在aspectd的编译上需要依赖dart源码中的kernalfront_end,可通过查看aspectd源码根目录中的pubspec.yaml查看依赖库和对应的ref。

dependency_overrides:
 kernel:
 git:
 url: https://github.com/dart-lang/sdk.git
 ref: 5e39817ec7ab7f56f381c244d105c7e40913a3e0
 path: pkg/kernel
 front_end:
 git:
 url: https://github.com/dart-lang/sdk.git
 ref: 5e39817ec7ab7f56f381c244d105c7e40913a3e0
 path: pkg/front_end

在1.2步骤中,使用git patch命令修改flutter源码引入了aspectd.dart文件,该文件做的核心操作就包括下载aspectd的依赖库、编译aspectd.dart.snapshot和根据注解内容使用aspect.dart.snapshot执行具体的dill transform操作。所以,aspectd是否生效的两个关键点是aspectd依赖库是否下载成功和aspectd.snapshot文件是否编译成功。
因为aspect使用依赖github源码指定ref的方式依赖kenerl和front_end库,这个过程需要下载github上dart-lang的所有源码(约900M左右),在国内的网络环境下很难做到一次成功,这里分享一个绕过因网络不稳定问题导致aspectd不生效的方法。

  1. 手动下载dart源码,git clone https://github.com/dart-lang/sdk.git

  2. 将dart源码切换到aspectd项目中pubspec.yaml指定的ref上,如上例中,可执行 git checkout 5e39817ec7ab7f56f381c244d105c7e40913a3e0

  3. 将aspect对github源码的依赖改成对本地源码的依赖


    image
  4. 手动编译aspect.dart.snapshot(在aspectd根目录中)
    dart --snapshot=snapshot/aspectd.dart.snapshot tool/starter.dart

  5. 修改flutter源码中的aspectd.dart,强制指定aspect.dart.snapshot的目录。


    image
  6. 删除flutter_tools.stamp重新编译运行flutter run --debug -v即可生效

1.5 常见问题解决

  • 等待另一个flutter命令释放锁
    Waiting for another flutter command to release the startup lock...
    解决方法,将bin/cache下的lockfile删除后重新执行命令
    rm ${path-for-git-flutter}/bin/cache/lockfile

  • 如何使用命令行编译工程
    debug版本:flutter run --debug --verbose
    release版本:flutter run --release --verbose

  • pub命令是什么?
    flutter pub get
    pub是dart提供的包管理工具,在flutter源码中的flutter/bin/cache/dart-sdk/bin/pub目录下有pub可执行文件,想要单独执行pub命令可讲该目录加入到系统的环境变量中
    相当于android gradle的gradle sync
    相当于ios pod中的pod install
    相当于js npm中的npm install

0x02 aspectd的注解

2.1 @pragma(‘vm:entry-point’)

在AOT变一下,如果不能被应用主入口(main)最终可能调用到,那么将被视为无用代码而被丢弃掉。AOP代码因为其注入逻辑的无侵入性,所以不会被main调用,因为使用此注解告诉编译器不要丢弃这段逻辑。

2.2 @Aspect

Aspect注解可以使得像asepctd源码example中aop_impl.dart这样的AOP实现类被方便的识别和提取,也可以起到方便开关的作用,如果想禁用掉这段AOP逻辑,移除@Aspect注解即可

2.3 @Call、@Execute、@Inject

在介绍这几个注解之前需要理解关于AOP的几个概念,aspectd官方介绍文档对aspectd的说明引入了很对对aop设计的说明,比如什么是Advice?什么是Before\Around\After?如果对这些概念没有预先的概念,读aspectd的文档是一头雾水的,至少我是这样!

2.3.1 什么是Joint Point(连接点)

能够插入切面的一个点。这个点可以是类的某个方法调用前、调用后、方法抛出异常后等。切面代码可以利用这些点插入到应用的正常流程之中,并添加行为

2.3.2 什么是Pointcut(切点)

指定一个通知将被引发的一系列连接点的集合。切点是连接点规则的描述。切点和连接点不是一对一的关系,一个切点匹配多个连接点

2.3.3 什么是Target Object(目标对象)

包含连接点的对象

2.3.3 什么是Advice(通知)

在特定的连接点,AOP框架执行的动作。通知有常见的几种类型:

  • 前置通知Before:在目标方法被调用之前调用通知功能
  • 后置通知After:目标方法完成之后调用通知,无论该方法是否发生异常
  • 后置返回通知After-returning:在目标方法成功执行之后调用通知
  • 后置异常通知After-throwing:在目标方法抛出异常后调用通知
  • 环绕通知Around:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为
2.3.4 @Call、@Execute、@Inject

aspectd只有一种统一的通知类型,就是Around。具体分为两种注解,分别是@Call和@Execute,这两种注解表达的PointCut都是通过包装原有方法实现的。差别是,@Call的PointCut是调用的地方,并不会修改原始方法的内部。@Execute会修改原有方法的内部。举个例子,分别使用@Call和@Execute对test方法执行切面操作

void test(){
 print("print hello world!")
}

void main(){
 test();
}

@Call表达注解的实际代码会变成这样:

void test(){
 print("print hello world!")
}

void invokeCall(){
 // to do somethings
 test();
 // to do somethings
}

void main(){
 aop:invokeCall()
}

@Execute表达注解的实际代码会变成这样:

void invokeExecutor(){
 // to do somethings
 print("print hello world!")
}

void test(){
 invokeExecutor();
}

void main(){
 test();
}

而@Inject相对于Call/Executor而言,多了一个lineNum的参数,用于指定插入逻辑的具体行号。用于在具体方法中间插入处理逻辑。

AOP 的理解是,在做日志、埋点追踪、安全检查时使用 AOP 可以在不扰乱正常业务代码的情况下添加想要的功能。

另外,在闲鱼团队的介绍文章中也提到,基于 AOP 可以对 flutter 执行非侵入式框架改造,这样可以实现例如自动化录制回放的功能。

框架整体理念
AspectD 通过在编译期操纵 AST 抽象语法树,达到对指定函数、方法添加调用监视和增加额外逻辑的目的。

引入 AspectD 将对项目结构产生一定改变,同时也要修改 flutter_tools 的少量源码。引入完成后,
运行项目的方式及入口与引入前一致。

AspectD 对项目结构的具体影响
假设引入前项目结构是

└─example
    │  pubspec.yaml
    │
    ├─android
    ├─ios
    └─lib
            main.dart  // 这是项目原有的入口

引入 AOP 之后的结构是

└─example
    │  pubspec.yaml
    │
    ├─android
    ├─aop
    │  │  pubspec.yaml
    │  │
    │  ├─android
    │  ├─ios
    │  └─lib
    │          aop.dart
    │
    ├─ios
    └─lib
            main.dart // 这依然是项目的入口

可以看到,引入 aop 相当于新引入了一个包(package)。AspectD 约定此包名为 aop(也支持自定义),此包的入口约定为 aop.dart。

这样实际上就把 aop 中的 logging 或者其他各种逻辑与业务逻辑不光从逻辑上,而且从物理上隔离开了。

AspectD 对 flutter_tools 的影响
AspectD 主要是通过在 build 环节增加对 AST 的操作所实现,所以在我们使用 flutter 编译项目时需要添加额外步骤。
这通过修改 flutter_tools 包来实现。主要在这个包中的 build 函数中增加了 AspectD 编译相关的调用(hook)。

笔者觉得这样对标准工具的相关的修改算是比较小,可以接受。

小结
闲鱼团队提供的这款工具可以实现 AOP 编程,有利于保持业务逻辑的清晰。

目前框架引入的门槛略高,需要打 git patch 到 flutter_tools 上,相应版本的配合指定得也还不明确。

总体上是一个很实用的框架。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容