内容简介:随着Flutter这一框架的快速发展,有越来越多的业务开始使用Flutter来重构或新建其产品。但在我们的实践过程中发现,一方面Flutter开发效率高,性能优异,跨平台表现好,另一方面Flutter也面临着插件,基础能力,底层框架缺失或者不完善等问题。举个栗子,我们在实现一个自动化录制回放的过程中发现,需要去修改Flutter框架(Dart层面)的代码才能够满足要求,这就会有了对框架的侵入性。要解决这种侵入性的问题,更好地减少迭代过程中的维护成本,我们考虑的首要方案即面向切面编程。
问题背景
随着Flutter这一框架的快速发展,有越来越多的业务开始使用Flutter来重构或新建其产品。但在我们的实践过程中发现,一方面Flutter开发效率高,性能优异,跨平台表现好,另一方面Flutter也面临着插件,基础能力,底层框架缺失或者不完善等问题。
举个栗子,我们在实现一个自动化录制回放的过程中发现,需要去修改Flutter框架(Dart层面)的代码才能够满足要求,这就会有了对框架的侵入性。要解决这种侵入性的问题,更好地减少迭代过程中的维护成本,我们考虑的首要方案即面向切面编程。
那么如何解决AOP for Flutter这个问题呢?本文将重点介绍一个闲鱼技术团队开发的针对Dart的AOP编程框架AspectD。
AspectD:面向Dart的AOP框架
AOP能力究竟是运行时还是编译时支持依赖于语言本身的特点。举例来说在iOS中,Objective C本身提供了强大的运行时和动态性使得运行期AOP简单易用。在Android下,Java语言的特点不仅可以实现类似AspectJ这样的基于字节码修改的编译期静态代理,也可以实现Spring AOP这样的基于运行时增强的运行期动态代理。 那么Dart呢?一来Dart的反射支持很弱,只支持了检查(Introspection),不支持修改(Modification);其次Flutter为了包大小,健壮性等的原因禁止了反射。
因此,我们设计实现了基于编译期修改的AOP方案AspectD。
设计详图
典型的AOP场景
下列AspectD代码说明了一个典型的AOP使用场景:
面向开发者的API设计
PointCut的设计
PointCut需要完备表征以怎么样的方式(Call/Execute等),向哪个Library,哪个类(Library Method的时候此项为空),哪个方法来添加AOP逻辑。 PointCut的数据结构:
其中包含了源代码信息(如库名,文件名,行号等),方法调用对象,函数名,参数信息等。 请注意这里的 @pragma('vm:entry-point') 注解,其核心逻辑在于Tree-Shaking。在AOT(ahead of time)编译下,如果不能被应用主入口(main)最终可能调到,那么将被视为无用代码而丢弃。AOP代码因为其注入逻辑的无侵入性,显然是不会被main调到的,因此需要此注解告诉编译器不要丢弃这段逻辑。 此处的proceed方法,类似AspectJ中的ProceedingJoinPoint.proceed()方法,调用pointcut.proceed()方法即可实现对原始逻辑的调用。原始定义中的proceed方法体只是个空壳,其内容将会被在运行时动态生成。
Advice的设计
此处的 @pragma("vm:entry-point") 效果同a中所述,pointCut对象作为参数传入AOP方法,使开发者可以获得源代码调用信息的相关信息,实现自身逻辑或者是通过pointcut.proceed()调用原始逻辑。
Aspect的设计
Aspect的注解可以使得ExecuteDemo这样的AOP实现类被方便地识别和提取,也可以起到开关的作用,即如果希望禁掉此段AOP逻辑,移除@Aspect注解即可。
AOP代码的编译
包含原始工程的main入口
从上文可以看到,aop.dart引入 import'package:example/main.dart'as app; ,这使得编译aop.dart时可包含整个example工程的所有代码。
Debug模式下的编译
在aop.dart中引入 import'aop_impl.dart'; 这使得aop_impl.dart中内容即便不被aop.dart显式依赖,也可以在Debug模式下被编译进去。
Release模式下的编译
在AOT编译(Release模式下),Tree-Shaking逻辑使得当aop_impl.dart中的内容没有被aop中main调用时,其内容将不会编译到dill中。通过添加 @pragma("vm:entry-point") 可以避免其影响。
当我们用AspectD写出AOP代码,透过编译aop.dart生成中间产物,使得dill中既包含了原始项目代码,也包含了AOP代码后,则需要考虑如何对其修改。在AspectJ中,修改是通过对Class文件进行操作实现的,在AspectD中,我们则对dill文件进行操作。
Dill操作
dill文件,又称为Dart Intermediate Language,是Dart语言编译中的一个概念,无论是Script Snapshot还是AOT编译,都需要dill作为中间产物。
Dill的结构
我们可以通过dart sdk中的vm package提供的dump_kernel.dart打印出dill的内部结构
Dill变换
dart提供了一种Kernel to Kernel Transform的方式,可以通过对dill文件的递归式AST遍历,实现对dill的变换。
基于开发者编写的AspectD注解,AspectD的变换部分可以提取出是哪些库/类/方法需要添加怎样的AOP代码,再在AST递归的过程中通过对目标类的操作,实现Call/Execute这样的功能。
一个典型的Transform部分逻辑如下所示 :
通过对于dill中AST对象的遍历(此处的visitMethodInvocation函数),结合开发者书写的AspectD注解(此处的 aspectdInfoMap 和aspectdItemInfo),可以对原始的AST对象(此处methodInvocation)进行变换,从而改变原始的代码逻辑,即Transform过程。
AspectD支持的语法
不同于AspectJ中提供的Before\Around\After三种预发,在AspectD中,只有一种统一的抽象即Around。 从是否修改原始方法内部而言,有Call和Execute两种,前者的PointCut是调用点,后者的PointCut则是执行点。
Call
Execute
Inject
仅支持Call和Execute,对于Flutter(Dart)而言显然很是单薄。一方面Flutter禁止了反射,退一步讲,即便Flutter开启了反射支持,依然很弱,并不能满足需求。 举个典型的场景,如果需要注入的dart代码里,x.dart文件的类y定义了一个私有方法m或者成员变量p,那么在aop_impl.dart中是没有办法对其访问的,更不用说多个连续的私有变量属性获得。另一方面,仅仅对方法整体进行操作可能是不够的,我们可能需要在方法的中间插入处理逻辑。 为了解决这一问题,AspectD设计了一种语法Inject,参见下面的例子: flutter库中包含了一下这段手势相关代码:
如果我们想要在onTapCancel之后添加一段对于instance和context的处理逻辑,Call和Execute是不可行的,而使用Inject后,只需要简单的几句即可解决
通过上述的处理逻辑,经过编译构建后的dill中的GestureDetector.build方法如下所示: 此外,Inject的输入参数相对于Call/Execute而言,多了一个lineNum的命名参数,可用于指定插入逻辑的具体行号。
构建流程支持
虽然我们可以通过编译aop.dart达到同时编译原始工程代码和AspectD代码到dill文件,再通过Transform实现dill层次的变换实现AOP,但标准的flutter构建(即flutter tools)并不支持这个过程,所以还是需要对构建过程做细微修改。 在AspectJ中,这一过程是由非标准 Java 编译器的Ajc来实现的。在AspectD中,通过对flutter tools打上应用Patch,可以实现对于AspectD的支持
实战与思考
基于AspectD,我们在实践中成功地移除了所有对于Flutter框架的侵入性代码,实现了同有侵入性代码同样的功能,支撑上百个脚本的录制回放与自动化回归稳定可靠运行。
从AspectD的角度看,Call/Execute可以帮助我们便捷实现诸如性能埋点(关键方法的调用时长),日志增强(获取某个方法具体是在什么地方被调用到的详细信息),Doom录制回放(如随机数序列的生成记录与回放)等功能。Inject语法则更为强大,可以通过类似源代码诸如的方式,实现逻辑的自由注入,可以支持诸如App录制与自动化回归(如用户触摸事件的录制与回放)等复杂场景。
进一步来说,AspectD的原理基于Dill变换,有了Dill操作这一利器,开发者可以自由地对Dart编译产物进行操作,而且这种变换面向的是近乎源代码级别的AST对象,不仅强大而且可靠。无论是做一些逻辑替换,还是是Json<-->模型转换等,都提供了一种新的视角与可能。
写在最后
AspectD作为闲鱼技术团队新开发的面向Flutter的AOP框架,已经可以支持主流的AOP场景并在Github开源,欢迎使用。
Aspectd for Flutter: https://github.com/alibaba-flutter/aspectd
如果你在使用过程中,有任何问题或者建议,欢迎提issue: https://github.com/alibaba-flutter/aspectd/issues 或者PR: https://github.com/alibaba-flutter/aspectd/pulls
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro CSS and HTML Design Patterns
Michael Bowers / Apress / April 23, 2007 / $44.99
Design patterns have been used with great success in software programming. They improve productivity, creativity, and efficiency in web design and development, and they reduce code bloat and complexit......一起来看看 《Pro CSS and HTML Design Patterns》 这本书的介绍吧!