溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Dart中怎么通過注解生成代碼

發(fā)布時間:2021-12-28 15:53:49 來源:億速云 閱讀:139 作者:小新 欄目:大數(shù)據(jù)

小編給大家分享一下Dart中怎么通過注解生成代碼,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

背景

最近在項目中使用到了Dart中的注解代碼生成技術(shù),這跟之前Java中APT+JavaPoet生成代碼那套技術(shù)還是有一些不同的地方,比如

  • Flutter中在禁用了dart:mirror,無法使用反射情況下如何得到類相關(guān)信息?

  • Dart的文件不限制是class,可以是function、class,因而在注解掃描的范圍不同的情況下如何拿到層層信息而不僅僅是toplevel信息?

  • 提取到注解信息時又是如何生成復(fù)雜的模板代碼?

在Flutter中究竟是如何解決上面的問題呢?下面將一步步揭開這神秘的面紗。

一個簡單的例子

先從一個簡單的例子感受下dart中如何通過注解生成代碼

  • 聲明一個注解,并使用注解

Dart中怎么通過注解生成代碼

在Dart中構(gòu)造器用const修飾就好,可以看出Dart的注解聲明起來比較簡單,不像java中還得有運行類型如RunTime、Source等

  • 解析注解的生成器

在Dart中我們一般使用source_gen中的GeneratorForAnnotation,該類繼承自Generator這個跟Java APT中的processor職責(zé)類似,需要在GeneratorForAnnotation的泛型中填入我們需要處理的注解

Dart中怎么通過注解生成代碼

  • 觸發(fā)生成器的Builer

有了上面的生成注解的生成器,我們還需要Builder來觸發(fā)

Dart中怎么通過注解生成代碼

  • 創(chuàng)建配置文件 build.yaml

Dart中怎么通過注解生成代碼

  • 運行builder

由于Flutter 禁用了dart:mirror無法使用反射,因此只能在通過命令在編譯期觸發(fā),執(zhí)行如下命令,將會看到生成的代碼

Dart中怎么通過注解生成代碼

Dart中怎么通過注解生成代碼

是不是感受到了Dart注解生成代碼的奇特之處了,有像Java中AnnotationProcessor Tool的Generator,但是又多了Builder和build.yaml,那么這些是如何相互配合運行生成注解的呢?

宏觀概念

使用望遠(yuǎn)鏡宏觀概覽整個過程

當(dāng)我們使用buildrunner的 build之后 觸發(fā)build,會去讀取build.yaml文件的配置信息,這個信息最終會被buildconfig.dart中的BuildConfig類讀取到,然后通過讀取到builder,上面例子的testBuilder,觸發(fā)了其中的注解生成器(TestGenerator),來對抽象語法樹進(jìn)行信息提取(由于source_gen封裝了語法分析庫analysis和資源處理庫build,這里實際上是屏蔽了語法分析過程),跟java一樣都是一個個Element,具體可以看下代碼的實現(xiàn)類

歸納一下主要有以下個核心部分:

用戶觸發(fā) - 文件掃描 - 詞法分析 - 注解提取 - 代碼生成

微觀探索

再使用放大鏡仔仔細(xì)細(xì)研究一下其中的細(xì)節(jié):

build.yaml配置

在Java中我們使用谷歌提供的AutoService注解來生成META-INF/services/javax.annotation.processing.Processor 文件關(guān)聯(lián)注解處理器,但是Flutter中的dart注解只能在編譯期做文章,因此需要一個配置告訴編譯器,觸發(fā)哪些builder,對應(yīng)的就是build.yaml文件, 先看一個build.yaml配置感受一下

Dart中怎么通過注解生成代碼

build.yaml 配置的信息,最終都會被 buildconfig.dart 中的 BuildConfig 類讀取到。關(guān)于參數(shù)說明,目前也沒有太多資料,這里推薦官方說明buildconfig,通過buildconfig包下的BuildeConfig解析

解析入口如下

Dart中怎么通過注解生成代碼

從build_config.dart中可以看到,主要解析4個大的部分,下面將挑選常用的2個進(jìn)行分析

Dart中怎么通過注解生成代碼

targets

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

Dart中怎么通過注解生成代碼

在TargetBuilderConfig中有3個常用的屬性

  • enable

當(dāng)前builder是否生效

  • generate_for

這個屬性比較重要,可以決定針對那些文件/文件夾做掃描,或者排除哪些文件 input_set.dart,使用如下

Dart中怎么通過注解生成代碼

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

  • options

這個屬性可以允許你以鍵值對形式攜帶一些配置數(shù)據(jù)到代碼生成器中,對應(yīng)的是BuildOption參數(shù),下面在解讀builder時候會再次講述。

builder

來一個builder

Dart中怎么通過注解生成代碼

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

在build.yaml文件中描述如上, Map

Dart中怎么通過注解生成代碼

更多配置可以參考builder_definition.dart

其中有2個重要的屬性單獨解釋一下

  • run_before

可以指定builder的運行順序,如果幾個buidler有互相依賴可以,比如在阿里的路由框架annotationroute中就使用到了這個屬性,可以看看其yaml文件,主要在路由框架中使用到了mustache4dart需要收集路由信息來填充模板,它的解法是使用兩個builder,一個用來收集信息(routeWriteBuilder),收集完之后給另一個builder(routeBuilder)結(jié)合mustache4dart模板來生成需要的路由表,具體可以參考其routegenerator.dart

  • auto_apply

Dart中怎么通過注解生成代碼

看文字可能理解起來可能有點晦澀,搞個圖來解釋一下,比如上圖 libB中使用了注解功能:

  • 當(dāng)我們將auto_apply設(shè)置成dependents時:

如果 注解package 是直接依賴在 libB 上的,那么只能在 libB 上正常使用注解,雖然 頂層Package 包依賴了 libB,但是依然無法正常使用該注解

  • 當(dāng)我們將autoapply設(shè)置成allpackages時:

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

  • 當(dāng)我們將autoapply設(shè)置成rootpackage時:

如果 注解package 是直接依賴在 libB 上的,那么只能在頂層 Package 上正常使用注解,雖然是 libB 上做的依賴,但是就是不能用,不過 注解package 是直接依賴在 頂層Package 上的時候,不管 autoapply 設(shè)置的是 dependents、allpackages 或者是 root_package 時,其實都是能正常使用的

關(guān)于source_gen

簡介

了解完了基本配置的yaml文件之后,不得不提source_gen這個強大的庫,

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

  • build庫主要是資源文件的處理

  • analyser庫是對dart文件生成語法結(jié)構(gòu) source_gen主要提處理dart源碼,可以通過注解生成代碼。

核心類介紹

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引用,這樣有個最大的好處就是引用問題不需要過于關(guān)注,要注意的是,需要使用 sourcegen|combining_builder,它會將所有.g文件進(jìn)行合并。

  • LibraryBuilder生成獨立的文件

  • PartBuilder自定義part文件

生成器Generator

并且source_gen封裝了一套Generator,以上的buidler接收Generator的集合,收集Generator的產(chǎn)出生成一份文件,Generator只是一個抽象類,具體實現(xiàn)類是GeneratorForAnnotation,默認(rèn)只能攔截到top-level級別的(后面會解釋)元素,會被注解生成器接受一個指定注解類型,即GeneratorForAnnotation是單注解處理器例如

Dart中怎么通過注解生成代碼

由于analyser提供了語法節(jié)點的抽象元素Element和其metadata字段,對應(yīng)ElementAnnotation,注解生成器可以檢查元素的metadata類型是否匹配聲明的注解類型,從而找出被注解的元素及元素所在上下文的信息,然后將這些信息包裝給使用者。

核心方法generateForAnnotatedElement例如我們有這樣一段注解代碼

Dart中怎么通過注解生成代碼

從上面可以看出主要覆寫了generateForAnnotatedElement方法,有三個關(guān)鍵參數(shù)

  • Element element

被 annotation 所修飾的元素,通過它可以獲取到元素的name、metadata、可見性等等。

Dart中怎么通過注解生成代碼更多api可以查看element

關(guān)于toplevel注解

前文提到只能攔截到toplevel級別的元素,因此class內(nèi)部的方法其實都沒有掃描到,這是由于dart 文件是不像java,一個文件只能對應(yīng)一個類,dart文件可以是function,也是是class或者其他,因此只能默認(rèn)攔截到top-level級別的,后面需要開發(fā)者自己手動處理,比如ClassElement提供了 methods、fields來給開發(fā)者進(jìn)一步處理注解的機會,下面展示了解析類中的方法,屬性也是類似的

Dart中怎么通過注解生成代碼Element除了ClassElementImpl外還有多個派生如 FunctionElementImpl、ParamElementImpl等,具體可以自行查閱。

  • ConstantReader annotation

表示注解對象,通過它可以提取到注解相關(guān)信息以及參數(shù)值 有兩個關(guān)鍵方法

  • read

  • peek

不同之處在于,如果read方法讀取了不存在的參數(shù)名,會拋出異常,peek則不會,而是返回null。

  • BuildStep buildStep

這一次構(gòu)建的信息,通過它可以獲取到一些輸入輸出信息,例如輸入文件名等。

核心代碼分析

source_gen也是從build庫的Builder封裝而來

Dart中怎么通過注解生成代碼sourcegen根據(jù)Builder實現(xiàn)自己的的Builder,根據(jù)不同的特點派生出 SharedPartBuilder、LibraryBuilder、PartBuilder

Dart中怎么通過注解生成代碼

這里面有個核心的 Generator

Dart中怎么通過注解生成代碼

在 Builder 運行時,會調(diào)用 Generator 的 generate方法,并傳入兩個重要的參數(shù):

  • library 可以獲取源代碼信息以及注解信息

  • buildStep 它表示構(gòu)建過程中的一個步驟,通過它,我們可以獲取一些文件的輸入輸出信息

其中l(wèi)ibrary 包含的源碼信息是一個個的 Element 元素,Element只是抽象類,具體還是一個個ClassElementImpl、FuncationElementImpl等。source_gen實現(xiàn)了該類 GeneratorForAnnotation

Dart中怎么通過注解生成代碼

其中 第2點中l(wèi)ibrary.annotatedWith(typeChecker)跟進(jìn)去看下

Dart中怎么通過注解生成代碼

代碼生成

  • 純字符串拼接

使用三引號語法,這種只能解決一些低級生成

  • mustach

預(yù)制模板,通過一定的規(guī)則,提取信息之后填充信息到模板中,一個典型的例子如下

Dart中怎么通過注解生成代碼

學(xué)習(xí)成本較低,適合一些固定格式的代碼生成,比如路由表,阿里的annotation_route框架就是采用這個,可以看下它的模板tpl

Dart中怎么通過注解生成代碼

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

Dart中怎么通過注解生成代碼

  • code_builder

非常強大,玩過java注解生成代碼的朋友一定熟悉javapoet,二者非常類似,code_builder可以細(xì)分為表達(dá)式、語句、函數(shù)、類等等,就是學(xué)習(xí)成本比較高,需要按照它的語法去生成對應(yīng)的代碼,比如生成一個類

Dart中怎么通過注解生成代碼生成一個表達(dá)式

Dart中怎么通過注解生成代碼


與java注解生成代碼的對比

Dart中怎么通過注解生成代碼

看完了這篇文章,相信你對“Dart中怎么通過注解生成代碼”有了一定的了解,如果想了解更多相關(guān)知識,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI