简介: 详解 dart 与 java 注解生成代码异同点
作者:闲鱼技术 - 龙湫
1、背景
最近在我的项目中应用到了 Dart 中的注解代码生成技术,这跟之前 Java 中 APT+JavaPoet 生成代码那套技术还是有一些不同的中央,比方
- Flutter 中在禁用了 dart:mirror,无奈应用反射状况下如何失去类相干信息?
- Dart 的文件不限度是 class,能够是 function、class,因此在注解扫描的范畴不同的状况下如何拿到层层信息而不仅仅是 toplevel 信息?
- 提取到注解信息时又是如何生成简单的模板代码?
在 Flutter 中到底是如何下面的问题呢?上面将一步步揭开这神秘的面纱。
2、一个简略的例子
先从一个简略的例子感触下 dart 中如何通过注解生成代码
- 申明一个注解,并应用注解
在 Dart 中结构器用 const 润饰就好,能够看出 Dart 的注解申明起来比较简单,不像 java 中还得有运行类型如 RunTime、Source 等
- 解析注解的生成器
在 Dart 中咱们个别应用 source_gen 中的 GeneratorForAnnotation, 该类继承自 Generator 这个跟 Java APT 中的 processor 职责相似, 须要在 GeneratorForAnnotation 的泛型中填入咱们须要解决的注解
- 触发生成器的 Builer
有了下面的生成注解的生成器,咱们还须要 Builder 来触发
- 创立配置文件 build.yaml
- 运行 builder
因为 Flutter 禁用了 dart:mirror 无奈应用反射,因而只能在通过命令在编译期触发,执行如下命令,将会看到生成的代码
是不是感触到了 Dart 注解生成代码的奇异之处了,有像 Java 中 AnnotationProcessor Tool 的 Generator,然而又多了 Builder 和 build.yaml,那么这些是如何相互配合运行生成注解的呢?
3、宏观概览
应用望远镜宏观概览整个过程
当咱们应用 build_runner 的 build 之后 触发 build,会去读取 build.yaml 文件的配置信息,这个信息最终会被
build_config.dart 中的 BuildConfig 类读取到,而后通过读取到 builder, 下面例子的 testBuilder,触发了其中的注解生成器(TestGenerator),来对形象语法树进行信息提取(因为 source_gen 封装了语法分析库 analysis 和资源解决库 build, 这里实际上是屏蔽了语法分析过程),跟 java 一样都是一个个 Element, 具体能够看下代码的实现类
演绎一下次要有以下个外围局部:
用户触发 – 文件扫描 – 词法剖析 – 注解提取 – 代码生成
4、宏观摸索
再应用放大镜仔仔细细钻研一下其中的细节:
4.1 build.yaml 配置
在 Java 中咱们应用谷歌提供的 AutoService 注解来生成 META-INF/services/javax.annotation.processing.Processor 文件关联注解处理器,然而 Flutter 中的 dart 注解只能在编译期做文章,因而须要一个配置通知编译器,触发哪些 builder,对应的就是 build.yaml 文件,
先看一个 build.yaml 配置感受一下
build.yaml 配置的信息,最终都会被 build_config.dart 中的 BuildConfig 类读取到。
对于参数阐明,目前也没有太多材料,这里举荐官网阐明 build_config, 通过 build_config 包下的 Builde_Config 解析
解析入口如下
从 build_config.dart 中能够看到,次要解析 4 个大的局部,上面将筛选罕用的 2 个进行剖析
4.1.1 targets
在 build_target.dart#BuildTarget 能够看到反对属性的形容,其中有个 builder 属性应用的比拟多
在 TargetBuilderConfig 中有 3 个罕用的属性
- enable
以后 builder 是否失效
- generate_for
这个属性比拟重要,能够决定针对那些文件 / 文件夹做扫描,或者排除哪些文件 input_set.dart,应用如下
在 json_seriable 的 build.yaml 中也能够看到它的 yaml 文件中对 generate_for 属性的应用
- options
这个属性能够容许你以键值对模式携带一些配置数据到代码生成器中,对应的是 BuildOption 参数,上面在解读 builder 时候会再次讲述
4.1.2 builder
来一个 builder
BuilderOptions 能够提取到下面的 option 属性配置
在 build.yaml 文件中形容如上,
Map 即 BuilderDefinition 信息,上面将介绍一下罕用的配置
更多配置能够参考 builder_definition.dart
其中有 2 个重要的属性独自解释一下
- run_before
能够指定 builder 的运行程序,如果几个 buidler 有相互依赖能够,比方在阿里的路由框架 annotation_route 中就应用到了这个属性,能够看看其 yaml 文件,次要在路由框架中应用到了 mustache4dart 须要收集路由信息来填充模板,它的解法是应用两个 builder,一个用来收集信息 (routeWriteBuilder),收集完之后给另一个 builder(routeBuilder) 联合 mustache4dart 模板来生成须要的路由表,具体能够参考其 route_generator.dart
- auto_apply
看文字可能了解起来可能有点艰涩,搞个图来解释一下,比方上图 libB 中应用了注解性能:
- 当咱们将 auto_apply 设置成 dependents 时:
如果 注解 package 是间接依赖在 libB 上的,那么只能在 libB 上失常应用注解,尽管 顶层 Package 包依赖了 libB,然而仍然无奈失常应用该注解
- 当咱们将 auto_apply 设置成 all_packages 时:
如果 注解 package 是间接依赖在 libB 上的,那么在 libB 和 顶层 Package 上都能失常应用注解
- 当咱们将 auto_apply 设置成 root_package 时:
如果 注解 package 是间接依赖在 libB 上的,那么只能在顶层 Package 上失常应用注解,尽管是 libB 上做的依赖,然而就是不能用, 不过 注解 package 是间接依赖在 顶层 Package 上的时候,不论 auto_apply 设置的是 dependents、all_packages 或者是 root_package 时,其实都是能失常应用的
4.2 对于 source_gen
4.2.1 简介
理解完了根本配置的 yaml 文件之后,不得不提 source_gen 这个弱小的库,
source_gen 基于官网的 analysis/build 提供了一系列敌对的封装,source_gen 基于 analyzer 和 build 库,其中
- build 库次要是资源文件的解决
- analyser 库是对 dart 文件生成语法结构
source_gen 次要提解决 dart 源码,能够通过注解生成代码。
4.2.2 外围类介绍
source_gen 从 build 库提供的 Builder 派生出本人的_builder,并且封装了 3 个
Builder (builder.dart)
|_Builder (builder.dart)
|-LibraryBuilder (builder.dart)
|-SharedPartBuilder (builder.dart)
|-PartBuilder (builder.dart)
• SharedPartBuilder
生成.g.dart 文件,相似 json_seriable 一样,应用中央须要用是 part of 援用,这样有个最大的益处就是援用问题不须要过于关注,要留神的是,须要应用 source_gen|combining_builder,它会将所有.g 文件进行合并。
• LibraryBuilder
生成独立的文件
• PartBuilder
自定义 part 文件
生成器 Generator
并且 source_gen 封装了一套 Generator,以上的 buidler 接管 Generator 的汇合, 收集 Generator 的产出生成一份文件,Generator 只是一个抽象类,具体实现类是 GeneratorForAnnotation,默认只能拦挡到 top-level 级别的 (前面会解释) 元素,会被注解生成器承受一个指定注解类型,即 GeneratorForAnnotation 是单注解处理器例如
因为 analyser 提供了语法节点的形象元素 Element 和其 metadata 字段,对应 ElementAnnotation,注解生成器能够查看元素的 metadata 类型是否匹配申明的注解类型,从而找出被注解的元素及元素所在上下文的信息,而后将这些信息包装给使用者。
外围办法 generateForAnnotatedElement
例如咱们有这样一段注解代码
从下面能够看出次要覆写了 generateForAnnotatedElement 办法,有三个要害参数
- Element element
被 annotation 所润饰的元素,通过它能够获取到元素的 name、metadata、可见性等等。
更多 api 能够查看 element
对于 toplevel 注解
前文提到只能拦挡到 toplevel 级别的元素,因而 class 外部的办法其实都没有扫描到,这是因为 dart 文件是不像 java,一个文件只能对应一个类,dart 文件能够是 function,也是是 class 或者其余,因而只能默认拦挡到 top-level 级别的,前面须要开发者本人手动解决,比方 ClassElement 提供了 methods、fields 来给开发者进一步解决注解的机会,上面展现了解析类中的办法,属性也是相似的
Element 除了 ClassElementImpl 外还有多个派生如 FunctionElementImpl、ParamElementImpl 等,具体能够自行查阅。
- ConstantReader annotation
示意注解对象,通过它能够提取到注解相干信息以及参数值
有两个要害办法
• read
• peek
不同之处在于,如果 read 办法读取了不存在的参数名,会抛出异样,peek 则不会,而是返回 null。
- BuildStep buildStep
这一次构建的信息,通过它能够获取到一些输入输出信息,例如输出文件名等
4.2.3 外围代码剖析
source_gen 也是从 build 库的 Builder 封装而来
source_gen 依据 Builder 实现本人的的_Builder, 依据不同的特点派生出 SharedPartBuilder、LibraryBuilder、PartBuilder
这外面有个外围的 Generator
在 Builder 运行时,会调用 Generator 的 generate 办法,并传入两个重要的参数:
- library 能够获取源代码信息以及注解信息
- buildStep 它示意构建过程中的一个步骤,通过它,咱们能够获取一些文件的输入输出信息
其中 library 蕴含的源码信息是一个个的 Element 元素,Element 只是抽象类,具体还是一个个 ClassElementImpl、FuncationElementImpl 等。
source_gen 实现了该类 GeneratorForAnnotation
其中 第 2 点中 library.annotatedWith(typeChecker)跟进去看下
5、对于代码生成
- 纯字符串拼接
应用三引号语法,这种只能解决一些低级生成
- mustach
预制模板,通过肯定的规定,提取信息之后填充信息到模板中,一个典型的例子如下
学习老本较低(理解 mustach 更多规定),适宜一些固定格局的代码生成,比方路由表,阿里的 annotation_route 框架就是采纳这个,能够看下它的模板 tpl,
而后应用了 2 个生成器,一个用来采集信息,另一个用来将采集后的信息注入到 mustach 模板中
- code_builder
十分弱小,玩过 java 注解生成代码的敌人肯定相熟 javapoet,二者十分相似,code_builder 能够细分为表达式、语句、函数、类等等,就是学习老本比拟高,须要依照它的语法去生成对应的代码,比方生成一个类
生成一个表达式
更多技巧须要看下源码去学习应用。
6、与 Java 注解生成代码比照
7、小结
本文初步摸索了在 Dart 通过注解生成代码的技术,比起 java 的 apt,没有运行时反射用起来还是有点点麻烦,须要手动执行 build,而且各种繁琐的 builder 配置,让人感觉艰涩难懂,生成代码的技巧也跟 java 有着殊途同归之妙,须要借助一些外力比方 mustach,code_builder 等。这种技术给咱们在解决一些例如路由,模板代码、动静代理等,多了一种解决伎俩,其余更多的应用场景须要咱们去开发中缓缓摸索。
参考
- mustache
- code_builder
- https://github.com/Reign9201/image_path_helper
- source_gen
- Flutter 注解解决及代码生成
- [[Part 2] Code generation in Dart: Annotations, source_gen and build_runner](https://medium.com/flutter-community/part-2-code-generation-in-dart-annotations-source-gen-and-build-runner-bbceee28697b))