写点什么

详解 Dart 中如何通过注解生成代码

  • 2020-08-11
  • 本文字数:4219 字

    阅读完需:约 14 分钟

详解Dart中如何通过注解生成代码

背景

最近在项目中使用到了 Dart 中的注解代码生成技术,这跟之前 Java 中 APT+JavaPoet 生成代码那套技术还是有一些不同的地方,比如


  • Flutter 中在禁用了 dart:mirror,无法使用反射情况下如何得到类相关信息?

  • Dart 的文件不限制是 class,可以是 function、class,因而在注解扫描的范围不同的情况下如何拿到层层信息而不仅仅是 toplevel 信息?

  • 提取到注解信息时又是如何生成复杂的模板代码?


在 Flutter 中究竟是如何解决上面的问题呢?下面将一步步揭开这神秘的面纱。

一个简单的例子

先从一个简单的例子感受下 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,那么这些是如何相互配合运行生成注解的呢?

宏观概念

使用望远镜宏观概览整个过程


当我们使用 buildrunner 的 build 之后 触发 build,会去读取 build.yaml 文件的配置信息,这个信息最终会被 buildconfig.dart 中的 BuildConfig 类读取到,然后通过读取到 builder,上面例子的 testBuilder,触发了其中的注解生成器(TestGenerator),来对抽象语法树进行信息提取(由于 source_gen 封装了语法分析库 analysis 和资源处理库 build,这里实际上是屏蔽了语法分析过程),跟 java 一样都是一个个 Element,具体可以看下代码的实现类


归纳一下主要有以下个核心部分:


用户触发 - 文件扫描 - 词法分析 - 注解提取 - 代码生成

微观探索

再使用放大镜仔仔细细研究一下其中的细节:

build.yaml 配置

在 Java 中我们使用谷歌提供的 AutoService 注解来生成 META-INF/services/javax.annotation.processing.Processor 文件关联注解处理器,但是 Flutter 中的 dart 注解只能在编译期做文章,因此需要一个配置告诉编译器,触发哪些 builder,对应的就是 build.yaml 文件,


先看一个 build.yaml 配置感受一下



build.yaml 配置的信息,最终都会被 buildconfig.dart 中的 BuildConfig 类读取到。关于参数说明,目前也没有太多资料,这里推荐官方说明 buildconfig,通过 buildconfig 包下的 BuildeConfig 解析


解析入口如下



从 build_config.dart 中可以看到,主要解析 4 个大的部分,下面将挑选常用的 2 个进行分析



targets


在 build_target.dart#BuildTarget 可以看到支持属性的描述,其中有个 builder 属性使用的比较多



在 TargetBuilderConfig 中有 3 个常用的属性


  • enable


当前 builder 是否生效


  • generate_for


这个属性比较重要,可以决定针对那些文件/文件夹做扫描,或者排除哪些文件 input_set.dart,使用如下



在 jsonseriable 的 build.yaml 中也可以看到它的 yaml 文件中对 generatefor 属性的使用


  • options


这个属性可以允许你以键值对形式携带一些配置数据到代码生成器中,对应的是 BuildOption 参数,下面在解读 builder 时候会再次讲述。


builder


来一个 builder



BuilderOptions 可以提取到上面的 option 属性配置


在 build.yaml 文件中描述如上,Map 即 BuilderDefinition 信息,下面将介绍一下常用的配置



更多配置可以参考 builder_definition.dart


其中有 2 个重要的属性单独解释一下


  • run_before


可以指定 builder 的运行顺序,如果几个 buidler 有互相依赖可以,比如在阿里的路由框架 annotationroute 中就使用到了这个属性,可以看看其 yaml 文件,主要在路由框架中使用到了 mustache4dart 需要收集路由信息来填充模板,它的解法是使用两个 builder,一个用来收集信息(routeWriteBuilder),收集完之后给另一个 builder(routeBuilder)结合 mustache4dart 模板来生成需要的路由表,具体可以参考其 routegenerator.dart


  • auto_apply



看文字可能理解起来可能有点晦涩,搞个图来解释一下,比如上图 libB 中使用了注解功能:


  • 当我们将 auto_apply 设置成 dependents 时:


如果 注解 package 是直接依赖在 libB 上的,那么只能在 libB 上正常使用注解,虽然 顶层 Package 包依赖了 libB,但是依然无法正常使用该注解


  • 当我们将 autoapply 设置成 allpackages 时:


如果 注解 package 是直接依赖在 libB 上的,那么在 libB 和 顶层 Package 上都能正常使用注解


  • 当我们将 autoapply 设置成 rootpackage 时:


如果 注解 package 是直接依赖在 libB 上的,那么只能在顶层 Package 上正常使用注解,虽然是 libB 上做的依赖,但是就是不能用,不过 注解 package 是直接依赖在 顶层 Package 上的时候,不管 autoapply 设置的是 dependents、allpackages 或者是 root_package 时,其实都是能正常使用的

关于 source_gen

简介


了解完了基本配置的 yaml 文件之后,不得不提 source_gen 这个强大的库,


sourcegen 基于官方的 analysis/build 提供了一系列友好的封装,sourcegen 基于 analyzer 和 build 库,其中


  • build 库主要是资源文件的处理

  • analyser 库是对 dart 文件生成语法结构 source_gen 主要提处理 dart 源码,可以通过注解生成代码。


核心类介绍



sourcegen 从 build 库提供的 Builder 派生出自己的 builder,并且封装了 3 个


Builder (builder.dart)|_Builder (builder.dart)|-LibraryBuilder (builder.dart)|-SharedPartBuilder (builder.dart)|-PartBuilder (builder.dart)
复制代码


  • SharedPartBuilder


生成.g.dart 文件,类似 jsonseriable 一样,使用地方需要用是 part of 引用,这样有个最大的好处就是引用问题不需要过于关注,要注意的是,需要使用 sourcegen|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


这一次构建的信息,通过它可以获取到一些输入输出信息,例如输入文件名等。


核心代码分析


source_gen 也是从 build 库的 Builder 封装而来



sourcegen 根据 Builder 实现自己的的 Builder,根据不同的特点派生出 SharedPartBuilder、LibraryBuilder、PartBuilder



这里面有个核心的 Generator



在 Builder 运行时,会调用 Generator 的 generate 方法,并传入两个重要的参数:


  • library 可以获取源代码信息以及注解信息

  • buildStep 它表示构建过程中的一个步骤,通过它,我们可以获取一些文件的输入输出信息


其中 library 包含的源码信息是一个个的 Element 元素,Element 只是抽象类,具体还是一个个 ClassElementImpl、FuncationElementImpl 等。source_gen 实现了该类 GeneratorForAnnotation



其中 第 2 点中 library.annotatedWith(typeChecker)跟进去看下


代码生成

  • 纯字符串拼接


使用三引号语法,这种只能解决一些低级生成


  • mustach


预制模板,通过一定的规则,提取信息之后填充信息到模板中,一个典型的例子如下



学习成本较低,适合一些固定格式的代码生成,比如路由表,阿里的 annotation_route 框架就是采用这个,可以看下它的模板 tpl



然后使用了 2 个生成器,一个用来采集信息,另一个用来将采集后的信息注入到 mustach 模板中



  • code_builder


非常强大,玩过 java 注解生成代码的朋友一定熟悉 javapoet,二者非常类似,code_builder 可以细分为表达式、语句、函数、类等等,就是学习成本比较高,需要按照它的语法去生成对应的代码,比如生成一个类



生成一个表达式



更多技巧需要看下源码去学习使用。

与 java 注解生成代码的对比

小结

本文初步探索了在 Dart 通过注解生成代码的技术,比起 java 的 apt,没有运行时反射用起来还是有点点麻烦,需要手动执行 build,而且各种繁琐的 builder 配置,让人感觉晦涩难懂,生成代码的技巧也跟 java 有着异曲同工之妙,需要借助一些外力比如 mustach,code_builder 等。这种技术给我们在解决一些例如路由,模板代码、动态代理等,多了一种处理手段,其他更多的使用场景需要我们去开发中慢慢探索。


参考



本文转载自公众号闲鱼技术(ID:XYtech_Alibaba)。


原文链接


https://mp.weixin.qq.com/s/ZA62prbsM6KwnHkBT4i7yQ


2020-08-11 10:002546

评论

发布
暂无评论
发现更多内容

区块链app开发要多少钱?如何根据项目需求了解价格?

2020年11期券商App行情刷新及交易体验评测报告

博睿数据

APM 数据 AIOPS 证券

纵观 ActiveX 平台的兴衰史,看开发控件的技术演变

葡萄城技术团队

SpreadJS activex

甲方日常 79

句子

工作 随笔杂谈 日常

区块链挖矿系统开发功能方案

区块链钱包开发的功能与特点

漫画 | 带你领略前端发展史的江湖恩怨情仇

苏南

程序员 大前端 漫画 时代发展

区块链钱包开发的核心优势

数字货币交易所开发的功能与特点

Flink SQL 实战:双流 join 场景应用

Apache Flink

flink 流计算

这道面试题,出错率90%

田维常

面试

RocketMQ消息模型

废材姑娘

Java RocketMQ

币币交易所系统开发详情说明

程序员修炼之路:你该知道的 7 个必经阶段

阿里巴巴云原生

阿里云 程序员 云原生 自我思考 成长笔记

7 天开发后台系统技术小结

老魚

程序员 全栈 建站

智慧社区管理平台建设,智慧平安小区整体解决方案

t13823115967

智慧社区安防系统平台开发

mysql binlog轻量同步工具binlog portal

dothetrick

Java MySQL springboot Binlog spring Boot Starter

区块链数字货币交易所开发的简介

Python的GIL

yunson

Python GIL

对冲基金的子基金模式vs集中管理

9527

国外低代码平台趟过那些坑,对国内低代码企业有哪些启示?

DT极客

LINUX SHELL脚本攻略

田维常

大作业1

蓝黑

我参与阿里巴巴 ASoC-Seata 的一些感悟

阿里巴巴云原生

阿里云 开发者 云原生 感悟 seata

智慧公安防控管理平台搭建,重点人员管控系统解决方案

t13823115967

智慧公安

有没有听说过通达快递?

escray

极客时间 极客大学 课程作业 大作业 架构师训练营第 1 期

《数据挖掘:实用的机器学习工具和技术,第4版》PDF版免费下载

计算机与AI

数据挖掘 机器学习 数据科学

区块链商城系统开发技术详解

千里公路建设尽收眼底,3D可视化监测管养运,领导都惊呆了

一只数据鲸鱼

物联网 数据可视化 3D可视化 公路建设 智慧交通

Ubuntu 使用 Iptables 做网络转发

wong

iptables Ubuntu20.04

数仓缓慢变化维深层讲解

大数据老哥

大数据 数据仓库 数仓

详解Dart中如何通过注解生成代码_大前端_龙湫_InfoQ精选文章