AspectD
AspectD 是一个用于 Dart 的 AOP(面向切面编程)框架。与其他传统的 AOP 框架一样,AspectD 提供了 call&execute 语法。此外,由于我们在 Flutter 中无法使用 dart:mirrors,AspectD 还提供了一种称为 inject 的方法来增强 Dart 代码操作。
此外,AspectD 在一个 dill transformer 容器中提供了这些功能,开发者可以在其上实现自己的 transformer,例如 hook、json、mirrors 等。
设计

假设您有一个名为 example 的 Flutter 项目,位于 hf_dir。
安装
1. 在 hf_dir/example 中创建一个名为 aspectd_impl 的 dart 包
flutter create --template=package aspectd_impl
2. 将 aspectd&example 依赖添加到 aspectd_impl 包
dependencies:
flutter:
sdk: flutter
aspectd:
git:
url: [email protected]:XianyuTech/aspectd.git
ref: stable/v2.2.3
example:
path: ../example
请记住,更改分支以匹配您的 Flutter 环境(目前支持 stable 分支)。
在 aspectd_impl 包中获取包依赖
flutter packages get
3. 修改 aspectd_impl 包
aspectd_impl.dart(入口点)
import 'package:example/main.dart' as app;
import 'aop_impl.dart';
void main()=> app.main();
aop_impl.dart(AOP 实现)
import 'package:aspectd/aspectd.dart';
@Aspect()
@pragma("vm:entry-point")
class ExecuteDemo {
@pragma("vm:entry-point")
ExecuteDemo();
@Execute("package:example/main.dart", "_MyHomePageState", "-_incrementCounter")
@pragma("vm:entry-point")
void _incrementCounter(PointCut pointcut) {
pointcut.proceed();
print('KWLM called!');
}
}
4. 修补 flutter_tools 以应用 aspectd.dart.snapshot
cd path-for-flutter-git-repo
git apply --3way path-for-aspectd-package/0001-aspectd.patch
rm bin/cache/flutter_tools.stamp
在 Windows 上,请使用 "git am --reject --whitespace=fix aspectd\0001-aspectd.patch" 来应用补丁。
由于 flutter_tools 目前不支持 hooks,因此 aspectd.patch 目前是必需的。随着 Flutter 的发展,此补丁有时可能会失败。但是,解决冲突会很简单,因为 AspectD 在构建 dill 时只添加两个 hooks。更多信息请参见 https://github.com/alibaba-flutter/aspectd/issues/5。
如果您想自定义 aspectd_impl 包,请编辑 path-for-flutter-git-repo/flutter/packages/flutter_tools/lib/src/aspectd.dart 中定义的 aspectdImplPackageRelPath(aspectd_impl 包相对于 example 的 pubspec.yaml 的相对路径)和 aspectdImplPackageName(aspectd_impl 包文件夹名称和主入口文件名)。
const String aspectdImplPackageRelPath = '..';
const String aspectdImplPackageName = 'aspectd_impl';
步骤 1-3 是每次想将 aspectd_impl 添加到 Flutter(Dart)包时需要运行的。步骤 4 只需要运行一次,除非 dart-sdk 发生变化。例如,如果您升级了 Flutter,则需要检查是否需要重新运行步骤 4。
如果您使用的是不包含本地生成的 aspectd_impl 包的 example,请记住在 aspectd_impl 包中运行 flutter packages get 来获取 aspectd 并检查步骤 4。
如果您想要的 Flutter 版本尚未支持,请参见 UPGRADE.md 以获取更多信息。
教程
现在 AspectD 提供了三种进行 AOP 编程的方法。
call
特定函数的每个调用点都将被操作。
import 'package:aspectd/aspectd.dart';
@Aspect()
@pragma("vm:entry-point")
class CallDemo{
@Call("package:app/calculator.dart","Calculator","-getCurTime")
@pragma("vm:entry-point")
Future<String> getCurTime(PointCut pointcut) async{
print('Aspectd:KWLM02');
print('${pointcut.sourceInfos.toString()}');
Future<String> result = pointcut.proceed();
String test = await result;
print('Aspectd:KWLM03');
print('${test}');
return result;
}
@Call("package:app/calculator.dart","Calculator","+getCurTemporature")
@pragma("vm:entry-point")
String getCurTemporature(PointCut pointcut) {
print('Aspectd:KWLM04');
print('${pointcut.sourceInfos.toString()}');
try{
String res = pointcut.proceed();
} catch (error, trace){
print('Aspectd:KWLM05');
}
return null;
}
@Call("package:flutter/src/widgets/binding.dart","","+runApp")
@pragma("vm:entry-point")
static void runAppKWLM(PointCut pointcut){
print('Aspectd:KWLM07');
print('${pointcut.sourceInfos.toString()}');
pointcut.proceed();
}
}
在这种情况下,请注意需要使用 @Aspect() 来标记一个类,以便 aspectd 知道该类包含 AspectD 注释信息。
需要 @pragma("vm:entry-point"),以便类/函数不会被 tree-shaking 移除。
对于 @Call("package:app/calculator.dart","Calculator","-getCurTime"),有几点需要注意。目前 call/execute/inject 接受三个位置参数:包名、类名(如果过程是库方法,则此部分为空字符串)和函数名。函数名可能带有前缀('-' 或 '+'),'-' 表示实例方法,'+' 表示库静态方法(如 main)和类方法。对于 inject,还有一个名为 lineNum 的命名参数,以便 AspectD 知道在哪个行注入代码片段。lineNum 参数从 1 开始,代码片段将被注入到该行之前。
此外,当您想操作静态方法(包括库方法和类方法)时,您的 AOP 方法(此处为 runAppKWLM)也应声明为静态。使用 execute 命令时也需要满足此要求。
execute
特定函数的每个实现都将被操作。
import 'package:aspectd/aspectd.dart';
@Aspect()
@pragma("vm:entry-point")
class ExecuteDemo{
@Execute("package:app/calculator.dart","Calculator","-getCurTime")
@pragma("vm:entry-point")
Future<String> getCurTime(PointCut pointcut) async{
print('Aspectd:KWLM12');
print('${pointcut.sourceInfos.toString()}');
Future<String> result = pointcut.proceed();
String test = await result;
print('Aspectd:KWLM13');
print('${test}');
return result;
}
@Execute("package:app/calculator.dart","Calculator","+getCurTemporature")
@pragma("vm:entry-point")
String getCurTemporature(PointCut pointcut) {
print('Aspectd:KWLM14');
print('${pointcut.sourceInfos.toString()}');
try{
String res = pointcut.proceed();
} catch (error, trace){
print('Aspectd:KWLM15');
}
return null;
}
@Execute("package:flutter/src/widgets/binding.dart","","+runApp")
@pragma("vm:entry-point")
static void runAppKWLM(PointCut pointcut){
print('Aspectd:KWLM17');
print('${pointcut.sourceInfos.toString()}');
pointcut.proceed();
}
}
inject
对于如下的原始函数:(package:flutter/src/widgets/gesture_detector.dart)
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel;
},
);
}
...
}
import 'package:aspectd/aspectd.dart';
import 'package:flutter/services.dart';
@Aspect()
@pragma("vm:entry-point")
class InjectDemo{
@Inject("package:flutter/src/widgets/gesture_detector.dart","GestureDetector","-build", lineNum:452)
@pragma("vm:entry-point")
static void onTapBuild() {
Object instance; //Aspectd Ignore
Object context; //Aspectd Ignore
print(instance);
print(context);
print('Aspectd:KWLM25');
}
}
之后,原始的 build 函数将如下所示:
@override
Widget build(BuildContext context) {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
if (onTapDown != null || onTapUp != null || onTap != null || onTapCancel != null) {
gestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance
..onTapDown = onTapDown
..onTapUp = onTapUp
..onTap = onTap
..onTapCancel = onTapCancel;
},
print(instance);
print(context);
print('Aspectd:KWLM25');
);
}
...
}
在使用注入时,请注意 //Aspectd Ignore 部分,我们需要成功编译 AOP 包,因此我们需要声明实例/上下文变量。但是,在注入到原始函数(此处为 build)时,变量声明
Object instance; //Aspectd Ignore
Object context; //Aspectd Ignore
将被丢弃,以避免覆盖原始变量。