aspectd
是咸鱼团队发布的一个针对Flutter的aop框架,在实际应用中可以起到非常好的作用,比如日志打点.
但是随着flutter 2.5的发布,这个项目并没有跟上适配进度,或许咸鱼团队也已经放弃这个项目了(难道真的是传说的kpi项目吗?).
我们的项目中也用到了aspectd,主要用于日志打点方面,但是它没有适配flutter2.5让我很难受,于是决定自己动手丰衣足食.
之前没有深入去了解aspectd的实现原理,基本是拿来就用的,现在仔细研究终于发现为什么咸鱼团队没有对它进行适配了,随着dart 2.14的发布,他们屏蔽了MethodInvocation
和 PropertyGet
的api,并不是像以前一样改改.看了一下源码,发现可以用InstanceInvocation
和 InstanceGet
代替,不过修改难度还是挺大的.
其实在用aspectd之前,我对aspectd的设计就抱着很多的疑问:
-
aspectd
为什么要对flutter_tools
的代码进行修改呢? Flutter的代码编译是通过frontend_server.dart.snapshot
这个dart程序进行的,我们只需要重新编译出可以支持aop功能的frontend_server.dart.snapshot
,何必去修改flutter_tools
?这样反而对以后flutter的版本升级造成不便 -
为什么要有
aspect_impl
这样的奇特设计?直接将注入代码放在主程序代码里,或者以plugin的方式被主程序引用不就好了吗?何必这么多此一举? -
aspectd
不支持hot reload
和hot restart
,每次修改了注入代码都需要冷启动,真的是带来了极大的不方便.是技术原因无法实现吗?
随后又研究了aspect的代码,带来了更多的疑问:
-
为什么要hook
package:vm/target/flutter.dart
这个类?实际上FrontendCompiler
这个类已经添加了ProgramTransformer
可以让我们对代码进行转换. -
Execute的功能实现起来是不是太过于繁琐了?每次注入都在PointCut这个类里面注入一个以aop_stub_命名新方法然后将注入的逻辑放在这个方法里.为什么要做这么复杂的逻辑实现呢?直接修改源代码中的
functionNode.body
让其调用注入方法的实现不就好了吗?
带着上面的几个疑问,我开始对aspectd进行了修改:
- 去掉了
aspect_impl
等设计 - 去掉了
flutter_tools
相关的代码实现 - 由于我的项目里暂时只用到了Execute的相关逻辑,暂时没有添加Inject和Call
- 优化了Execute的实现
- 修改了AspectTransformer的入口
- 去掉了注入的标签,用系统自带的pragma
以下为注入Demo代码:
@pragma('vm:entry-point')
@pragma("aopd:inject", {
"importUri": "package:example/main.dart",
"clsName": "_MyHomePageState",
"methodName": "-_test2",
"isRegex": false
})
//必须是static,必然不起作用
static Future<bool> _test2(
Object target,
String functionName,
List<dynamic> positionalParams,
Map<String, dynamic> namedParams,
Function proceed) {
print(
"[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
return Function.apply(
proceed, positionalParams, _transToNamedParams(namedParams));
}
以下为部分修改代码逻辑: frontend_server.dart.snapshot入口设计:
import 'dart:io';
import 'package:args/args.dart';
import 'package:frontend_server/frontend_server.dart' as frontend;
import 'package:vm/target/flutter.dart';
import 'transformer.dart';
Future<void> main(List<String> args) async {
try {
ArgResults options = frontend.argParser.parse(args);
frontend.FrontendCompiler compiler = frontend.FrontendCompiler(stdout,
printerFactory: frontend.BinaryPrinterFactory(),
//添加注入逻辑
transformer: AspectAopTransformer(),
unsafePackageSerialization: options['unsafe-package-serialization'],
incrementalSerialization: options['incremental-serialization'],
useDebuggerModuleNames: options['debugger-module-names'],
emitDebugMetadata: options['experimental-emit-debug-metadata'],
emitDebugSymbols: options['emit-debug-symbols']);
//FlutterTarget.flutterProgramTransformer = AspectAopTransformer();
final int exitCode = await frontend.starter(args, compiler: compiler);
if (exitCode != 0) {
exit(exitCode);
}
} catch (error) {
print('ERROR: $error\n');
print(frontend.usage);
return;
}
}
以下为注入的主要代码实现:
//判断该Procedure是否已经被注入过,如果被注入过则应该略过
bool isInjectBlock(
Block? block, Procedure injectProcedure, Procedure originProcedure) {
if (block == null) {
return false;
}
if (block.statements.length != 1) {
return false;
}
Expression? expression;
if (block.statements.first is ReturnStatement) {
expression = (block.statements.first as ReturnStatement).expression;
} else if (block.statements.first is ExpressionStatement) {
expression = (block.statements.first as ExpressionStatement).expression;
} else {
return false;
}
if (expression is StaticInvocation) {
if (expression.arguments.positional.length != 5) {
print(
"[AspectAopTransformer] arguments is not 5 so return ${expression.arguments.positional.length}");
return false;
}
if (expression.arguments.positional[1] is StringLiteral &&
expression.arguments.positional[2] is ListLiteral &&
expression.arguments.positional[3] is MapLiteral &&
expression.arguments.positional[4] is FunctionExpression) {
if ((expression.arguments.positional[1] as StringLiteral).value ==
originProcedure.name.text) {
return true;
}
return false;
}
return false;
} else {
print(
"[AspectAopTransformer] expression is not StaticInvocation so return ");
return false;
}
}
//对方法进行注入操作
void transformInstanceMethodProcedure(
Library? originalLibrary, AopItem aopItem, Procedure originalProcedure) {
if (AopUtils.manipulatedProcedureSet.contains(originalProcedure)) {
//如果已经处理完过,就直接返回
return;
}
AopUtils.manipulatedProcedureSet.add(originalProcedure);
//FunctionNode 中定义了方法的参数和、body、返回值类型等
final FunctionNode functionNode = originalProcedure.function;
//当前方法的处理逻辑
Block? bodyStatements = functionNode.body as Block?;
if (bodyStatements == null) {
print(
"[AspectAopTransformer] bodyStatements ${originalProcedure.name.toString()} is null so return");
return;
}
//bodyStatements = getInjectBlock(bodyStatements, originalProcedure);
//检查当前方法是否已经被注入
if (isInjectBlock(
bodyStatements, aopItem.aopMember as Procedure, originalProcedure)) {
print(
"[AspectAopTransformer] isInjectBlock1 ${originalProcedure.name.toString()} so return");
return;
}
//是否需要返回
final bool shouldReturn =
!(originalProcedure.function.returnType is VoidType);
//将aspect相关的包导入源码文件
AopUtils.insertLibraryDependency(
originalLibrary!, aopItem.aopMember.parent!.parent as Library?);
//获取原方法的参数
Arguments originArguments =
AopUtils.argumentsFromFunctionNode(functionNode);
String functionName = originalProcedure.name.text;
//创建调用静态方法的参数
final Arguments redirectArguments = Arguments.empty();
//target
if (originalProcedure.isStatic) {
redirectArguments.positional.add(StringLiteral(functionName));
} else {
redirectArguments.positional.add(ThisExpression());
}
//functionName
redirectArguments.positional.add(StringLiteral(functionName));
//positionalParams
redirectArguments.positional.add(ListLiteral(originArguments.positional));
//namedParams
final List<MapLiteralEntry> entries = <MapLiteralEntry>[];
for (NamedExpression namedExpression in originArguments.named) {
//这里用SymbolLiteral貌似无效,退而求其次用StringLiteral
entries.add(MapLiteralEntry(
StringLiteral(namedExpression.name), namedExpression.value));
}
redirectArguments.positional.add(MapLiteral(entries));
//proceed
final FunctionNode newFunctionNode = FunctionNode(bodyStatements,
typeParameters: AopUtils.deepCopyASTNodes<TypeParameter>(
functionNode.typeParameters),
positionalParameters: functionNode.positionalParameters,
namedParameters: functionNode.namedParameters,
requiredParameterCount: functionNode.requiredParameterCount,
returnType: shouldReturn
? AopUtils.deepCopyASTNode(functionNode.returnType)
: const VoidType(),
asyncMarker: functionNode.asyncMarker,
dartAsyncMarker: functionNode.dartAsyncMarker);
FunctionExpression newFunctionExpression =
FunctionExpression(newFunctionNode);
redirectArguments.positional.add(newFunctionExpression);
//创建静态方法调用
final StaticInvocation callExpression =
StaticInvocation(aopItem.aopMember as Procedure, redirectArguments);
Block block = AopUtils.createProcedureBodyWithExpression(
callExpression, shouldReturn);
//将原本的处理流程替换成注入后的流程
functionNode.body = block;
print(
"[AspectAopTransformer] inject ${originalProcedure.name.toString()} success");
}
修改完成后发现hot reload,hot restart意外的能够用了,开心
以上实现已经提交到github上:github.com/lancexin/as…
如果不想自己编译,但是又想直接拿来用,可以执行下载编译好的frontend_server.dart.snapshot,覆盖 flutter_macos_stable/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot就可以直接拿来用了.
后记
发现这么做时AspectTransformer是在代码优化后执行的,如果是在release下,会发现namedParams会优化成positionalParams,而且部分constant参数并不会传入,因此AspectTransformer执行在aot的模式下必须是在优化前触发,如果这么做的话不得不修改vm 和front_end中的部分实现逻辑了:
在dart-sdk中 package:frontend_server/frontend_server.dart 做以下修改:
export 'package:vm/kernel_front_end.dart';
//将ProgramTransformer接口移动到 package:vm/kernel_front_end.dart中
//abstract class ProgramTransformer {
// void transform(Component component);
//}
...
results = await _runWithPrintRedirection(
() => compileToKernel(_mainSource, compilerOptions,
includePlatform: options['link-platform'],
deleteToStringPackageUris: options['delete-tostring-package-uri'],
aot: options['aot'],
useGlobalTypeFlowAnalysis: options['tfa'],
environmentDefines: environmentDefines,
enableAsserts: options['enable-asserts'],
useProtobufTreeShakerV2: options['protobuf-tree-shaker-v2'],
minimalKernel: options['minimal-kernel'],
treeShakeWriteOnlyFields: options['tree-shake-write-only-fields'],
fromDillFile: options['from-dill'],
//调用compileToKernel时传入 transformer
transformer: transformer),
);
...
if (results.component != null) {
//只有增量热更新才触发transformer
if (options['incremental']) {
transformer?.transform(results.component);
}
在dart-sdk中 package:fpackage:vm/kernel_front_end.dart 做以下修改:
//修改compileToKernel增添transformer逻辑
Future<KernelCompilationResults> compileToKernel(
Uri source, CompilerOptions options,
{bool includePlatform: false,
List<String> deleteToStringPackageUris: const <String>[],
bool aot: false,
bool useGlobalTypeFlowAnalysis: false,
required Map<String, String> environmentDefines,
bool enableAsserts: true,
bool useProtobufTreeShakerV2: false,
bool minimalKernel: false,
bool treeShakeWriteOnlyFields: false,
String? fromDillFile: null,
ProgramTransformer? transformer}) async {
...
if (deleteToStringPackageUris.isNotEmpty && component != null) {
to_string_transformer.transformComponent(
component, deleteToStringPackageUris);
}
//在aot优化前执行transform
if (component != null) {
transformer?.transform(component);
}
// Run global transformations only if component is correct.
if ((aot || minimalKernel) && component != null) {
await runGlobalTransformations(target, component, useGlobalTypeFlowAnalysis,
enableAsserts, useProtobufTreeShakerV2, errorDetector,
minimalKernel: minimalKernel,
treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
if (minimalKernel) {
// compiledSources is component.uriToSource.keys.
// Make a copy of compiledSources to detach it from
// component.uriToSource which is cleared below.
compiledSources = compiledSources!.toList();
component.metadata.clear();
component.uriToSource.clear();
}
}
...
}