@pragma('vm:entry-point')
在AOT编译中,如果没有被引用到的代码会丢弃掉,而AOP代码是不会被引用,需要使用该方式告诉编译器不要丢弃代码
PointCut
/// Object carrying callsite information and methods which can enable you to
/// call the original implementation.
@pragma('vm:entry-point')
class PointCut {
/// PointCut default constructor.
@pragma('vm:entry-point')
PointCut(this.sourceInfos, this.target, this.function, this.stubKey,
this.positionalParams, this.namedParams);
/// Source infomation like file, linenum, etc for a call.
final Map<dynamic, dynamic> sourceInfos;
/// Target where a call is operating on, like x for x.foo().
final Object target;
/// Function name for a call, like foo for x.foo().
final String function;
/// Unique key which can help the proceed function to distinguish a
/// mocked call.
final String stubKey;
/// Positional parameters for a call.
final List<dynamic> positionalParams;
/// Named parameters for a call.
final Map<dynamic, dynamic> namedParams;
/// Unified entrypoint to call a original method,
/// the method body is generated dynamically when being transformed in
/// compile time.
@pragma('vm:entry-point')
Object proceed() {
return null;
}
}
该对象中包含一些我们要调用或执行的代码的信息。包括源代码信息(如库名、文件名、行号等),方法调用对象,方法名,位置参数,命名参数等
proceed()是调用原始方法的入口点,pointcut.proceed()可实现对原始逻辑的调用。原始定义中的proceed方法体只是个空壳,其内容将会被在运行时动态生成
Aspect
/// Annotation indicating whether a class should be taken into consideration
/// when searching for aspectd implementations like AOP.
@pragma('vm:entry-point')
class Aspect {
/// Aspect default constructor
const factory Aspect() = Aspect._;
@pragma('vm:entry-point')
const Aspect._();
}
用来标记要进行AOP操作的类,方便AOP进行识别和提取,也可以起到开关的作用,如果希望禁掉此段AOP逻辑,移除@Aspect注解即可。
Call
@Aspect()
@pragma("vm:entry-point")
class RegularCallDemo {
@pragma("vm:entry-point")
RegularCallDemo();
@Call("package:example/main.dart", "", "+appInit")
@pragma("vm:entry-point")
static dynamic appInit(PointCut pointcut) {
print('[KWLM1]: Before appInit!');
dynamic object = pointcut.proceed();
print('[KWLM1]: After appInit!');
return object;
}
@Call("package:example/main.dart", "MyApp", "+MyApp")
@pragma("vm:entry-point")
static dynamic myAppDefine(PointCut pointcut) {
print('[KWLM2]: MyApp default constructor!');
return pointcut.proceed();
}
@Call("package:example/main.dart", "MyHomePage", "+MyHomePage")
@pragma("vm:entry-point")
static dynamic myHomePage(PointCut pointcut) {
dynamic obj = pointcut.proceed();
print('[KWLM3]: MyHomePage named constructor!');
return obj;
}
}
@Call("package:example/main.dart", "MyApp", "+MyApp")中第一个参数表示的是包名,第二个参数是类名,第三个参数是方法名。其中方法名可以有一个前缀(-或+),-表示的是实例方法。而+表示的是静态方法或类方法
需要注意的是:写的aop方法的类型要和要注入的方法一致,即带有+的,方法前要有static。
Execute
@Aspect()
@pragma("vm:entry-point")
class RegularExecuteDemo {
@pragma("vm:entry-point")
RegularExecuteDemo();
@Execute("package:example/main.dart", "_MyHomePageState", "-_incrementCounter")
@pragma("vm:entry-point")
dynamic _incrementCounter(PointCut pointcut) {
dynamic obj = pointcut.proceed();
print('[KWLM21]:${pointcut.sourceInfos}:${pointcut.target}:${pointcut.function}!');
return obj;
}
@Execute("dart:math", "Random", "-next.*", isRegex: true)
@pragma("vm:entry-point")
static dynamic randomNext(PointCut pointcut) {
dynamic obj = pointcut.proceed();
print('[KWLM22]:randomNext!');
return obj;
}
}
Call和Execute的区别是插入代码的位置不同,call是插入到调用的地方,而Execute是插入到执行的地方。例如:在main方法中调用say方法,通过Call方式插入的代码会插入到main方法中,而通过Execute方式插入的代码会插入到say方法中。
Inject
@Aspect()
@pragma("vm:entry-point")
class InjectDemo{
@Inject("package:example/main.dart","","+injectDemo", lineNum:27)
@pragma("vm:entry-point")
static void onInjectDemoHook1() {
print('Aspectd:KWLM51');
}
@Inject("package:example/main.dart","C","+fc", lineNum:198)
@pragma("vm:entry-point")
static void onInjectDemoHook3() {
print('Aspectd:KWLM52++++');
}
}
inject就是往具体的行前插入代码。
如何使修改代码生效
对于不同位置的代码,修改后让其生效需要做的不同。分为example、aspect_impl、aspectd下的lib
example中的代码
当只修改了example中的代码时,可以直接运行就会生效
aspect_impl中的代码
当修改了aspect_impl中的代码时,需要分别在aspect_impl和example中执行flutter clean和flutter pub get之后才能生效。
aspectd下的lib下的代码
当修改了这下面的代码后,需要将flutter_frontend_server目录下的frontend_server.dart.snapshot文件删除掉,然后分别在aspect_impl和example中执行flutter clean和flutter pub get之后才能生效。
当更改flutter版本
需要重新执行如下:
git apply --3way path-for-aspectd-package/0001-aspectd.patch
rm bin/cache/flutter_tools.stamp
通过上一节分析我们知道,git apply之后,flutter源码中会新增aspectd.dart文件,该文件中会判断frontend_server.dart.snapshot是否存在,存在就使用,不存在就通过代码重新生成,所以当我们修改了lib下的代码后需要删掉frontend_server.dart.snapshot。