Flutter 註解處理及代碼生成

十九世紀中期一批不同凡響的猿猴誕生了,他們排斥重複的工做,畢生都在追求效率和性能。而用代碼去生成代碼,是這些猴子的一點小聰明。java

猴子說:「一家人就要整整齊齊!」 因此即便是新興的Flutter,也被猴子們賦予了這樣的能力。git

本文首先將用一個簡單的demo帶你對Flutter,其實也就是 Dart 的註解處理和代碼生成有一個初步的認識。github

而後會對註解處理的各個環節和Api進行詳細講解,幫你去除初步認識過程當中產生的各類疑惑,學會使用Dart註解處理。緩存

爲了簡化描述,後文中[Dart註解處理],咱們直接用 Dart-APT 表示。bash

再以後咱們將會拿 Java-APT 與 Dart-APT 作一個對比,一方面強化你的認知,一方面介紹 Dart-APT 很是特殊的幾個要點。app

最後咱們將對 Dart-APT 的 Generator 進行簡要的源碼分析,幫助你更深刻的理解和使用Dart-APT。async

本文大綱:ide

  • 1.初識 Dart-APT
  • 2.Dart-APT Api詳解
  • 3.Java-APT & Dart-APT對比以及 Dart-APT 的特殊性
  • 4.Dart-APT Generator 源碼淺析

初識 Dart 註解處理以及代碼生成

第一節我先帶你以最簡單的demo,快速認識一下Flutter的註解處理和代碼生成的樣子,具體的API細節咱們放後面細細道來。源碼分析

Flutter,其實也就是Dart的註解處理依賴於 source_gen。它的詳細資料能夠在它的 Github 主頁查看,這裏咱們不作過多展開,你只須要知道[ Dart-APT Powered by source_gen]性能

在Flutter中應用註解以及生成代碼僅需一下幾個步驟:

  • 1.依賴 source_gen
  • 2.建立註解
  • 3.建立生成器
  • 4.建立Builder
  • 5.編寫配置文件

1.依賴 source_gen

第一步,在你工程的 pubspec.yaml 中引入 source_gen。若是你僅在本地使用且不打算將這個代碼當作一個庫發佈出去:

dev_dependencies:
  source_gen:
複製代碼

不然

dependencies:
  source_gen:
複製代碼

2.建立註解和使用

比起 java 中的註解建立,Dart 的註解建立更加樸素,沒有多餘的關鍵字,實際上只是一個構造方法須要修飾成 const 的普通 Class 。

例如,申明一個沒有參數的註解:

class TestMetadata {
  const TestMetadata();
}
複製代碼

使用:

@TestMetadata()
class TestModel {}
複製代碼

申明一個有參數的註解:

class ParamMetadata {
  final String name;
  final int id;

  const ParamMetadata(this.name, this.id);
}

複製代碼

使用:

@ParamMetadata("test", 1)
class TestModel {}
複製代碼

3.建立生成器

相似 Java-APT 的 Processor ,在 Dart 的世界裏,具備相同職責的是 Generator。

你須要建立一個 Generator,繼承於 GeneratorForAnnotation, 並實現: generateForAnnotatedElement 方法。

還要在 GeneratorForAnnotation 的泛型參數中填入咱們要攔截的註解。

class TestGenerator extends GeneratorForAnnotation<TestMetadata> {
  @override
  generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    return "class Tessss{}";
  }
}
複製代碼

返回值是一個 String,其內容就是你將要生成的代碼。

你能夠經過 generateForAnnotatedElement 方法的三個參數獲取註解的各類信息,用來生成相對應的代碼。三個參數的具體使用咱們後面細講。

這裏咱們僅簡單的返回一個字符串 "class Tessss{}",用來看看效果。

4.建立Builder

Generator 的執行須要 Builder 來觸發,因此如今咱們要建立一個Builder。

很是簡單,只須要建立一個返回類型爲 Builder 的全局方法便可:

Builder testBuilder(BuilderOptions options) =>
    LibraryBuilder(TestGenerator());
複製代碼

方法名隨意,重點須要關注的是返回的對象。

示例中咱們返回的是 LibraryBuilder 對象,構造方法的參數是咱們上一步建立的TestGenerator對象。

實際上根據不一樣的需求,咱們還有其餘Builder對象可選,Builder 的繼承樹:

  • Builder
    • _Builder
      • PartBuilder
      • LibraryBuilder
      • SharedPartBuilder
    • MultiplexingBuilder

PartBuilder 與 SharedPartBuilder 涉及到 dart-part 關鍵字的使用,這裏咱們暫時不作展開,一般狀況下 LibraryBuilder 已足以知足咱們的需求。 MultiplexingBuilder 支持多個Builder的添加。

5.建立配置文件

在項目根目錄建立 build.yaml 文件,其意義在於 配置 Builder 的各項參數:

builders:
  testBuilder:
    import: "package:flutter_annotation/test.dart"
    builder_factories: ["testBuilder"]
    build_extensions: {".dart": [".g.part"]}
    auto_apply: root_package
    build_to: source
複製代碼

配置信息的詳細含義咱們後面解釋。重點關注的是,經過 import 和 builder_factories 兩個標籤,咱們指定了上一步建立的 Builder。

6.運行 Builder

命令行中執行命令,運行咱們的 Builder

$ flutter packages pub run build_runner build
複製代碼

受限於Flutter 禁止反射的緣故,你不能再像Android中使用編譯時註解那樣,coding 階段使用接口,編譯階段生成實現類,運行階段經過反射建立實現類對象。在Flutter中,你只能先經過命令生成代碼,而後再直接使用生成的代碼。

能夠看到命令仍是偏長的,一個可行的建議是將命令封裝成一個腳本。

不出意外的話,命令執行成功後將會生成一個新的文件:TestModel.g.dart 其內容:

// GENERATED CODE - DO NOT MODIFY BY HAND

// **************************************************************************
// TestGenerator
// **************************************************************************

class Tessss {}

複製代碼

代碼生成成功!

清理生成的文件無需手動刪除,可執行如下命令:

flutter packages pub run build_runner clean
複製代碼

Dart-APT Api詳解

  • 1.註解建立與使用
  • 2.建立生成器 Generator
  • 3.generateForAnnotatedElement 參數: element
  • 4.generateForAnnotatedElement 參數: annotation
  • 5.generateForAnnotatedElement 參數: buildStep
  • 6.模板代碼生成技巧
  • 7.配置文件字段含義

1.註解建立與使用

Dart的註解建立和普通的class建立沒有任何區別,能夠 extends, 能夠 implements ,甚至能夠 with。

惟一必須的要求是:構造方法須要用 const 來修飾。

不一樣於java註解的建立須要指明@Target(定義能夠修飾對象範圍)

Dart 的註解沒有修飾範圍,定義好的註解能夠修飾類、屬性、方法、參數。

但值得注意的是,若是你的 Generator 直接繼承自 GeneratorForAnnotation, 那你的 Generator 只能攔截到 top-level 級別的元素,對於類內部屬性、方法等沒法攔截,類內部屬性、方法修飾註解暫時沒有意義。(不過這個事情擴展一下確定能夠實現的啦~)

2.建立生成器 Generator

Generator 爲建立代碼而生。一般狀況下,咱們將繼承 GeneratorForAnnotation,並在其泛型參數中添加目標 annotation。而後複寫 generateForAnnotatedElement 方法,最終 return 一個字符串,即是咱們要生成的代碼。

class TestGenerator extends GeneratorForAnnotation<TestMetadata> {
  @override
  generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    return "class Tessss{}";
  }
}
複製代碼

GeneratorForAnnotation的注意點有:

2.1 GeneratorForAnnotation與annotation的對應關係

GeneratorForAnnotation是單註解處理器,每個 GeneratorForAnnotation 必須有且只有一個 annotation 做爲其泛型參數。也就是說每個繼承自GeneratorForAnnotation的生成器只能處理一種註解。

2.2 generateForAnnotatedElement 參數含義

最值得關注的是 generateForAnnotatedElement 方法的三個參數:Element element, ConstantReader annotation, BuildStep buildStep。咱們生成代碼所依賴的信息均來自這三個參數。

  • Element element:被 annotation 所修飾的元素,經過它能夠獲取到元素的name、可見性等等。
  • ConstantReader annotation:表示註解對象,經過它能夠獲取到註解相關信息以及參數值。
  • BuildStep buildStep:這一次構建的信息,經過它能夠獲取到一些輸入輸出信息,例如輸入文件名等

generateForAnnotatedElement 的返回值是一個 String,你須要用字符串拼接出你想要生成的代碼,return null 意味着不須要生成文件。

2.3 代碼與文件生成規則

不一樣於java apt,文件生成徹底由開發者自定義。GeneratorForAnnotation 的文件生成有一套本身的規則。

在不作其餘深度定製的狀況下,若是 generateForAnnotatedElement 的返回值 永不爲空,則:

  • 若一個源文件僅含有一個被目標註解修飾的類,則每個包含目標註解的文件,都對應一個生成文件;

  • 若一個源文件含有多個被目標註解修飾的類,則生成一個文件,generateForAnnotatedElement方法被執行屢次,生成的代碼經過兩個換行符拼接後,輸出到該文件中。

3.generateForAnnotatedElement 參數: Element

例如咱們有這樣一段代碼,使用了 @TestMetadata 這個註解:

@ParamMetadata("ParamMetadata", 2)
@TestMetadata("papapa")
class TestModel {
  int age;
  int bookNum;

  void fun1() {}

  void fun2(int a) {}
}

複製代碼

在 generateForAnnotatedElement 方法中,咱們能夠經過 Element 參數獲取 TestModel 的一些簡單信息:

element.toString: class TestModel
element.name: TestModel
element.metadata: [@ParamMetadata("ParamMetadata", 2),@TestMetadata("papapa")] 
element.kind: CLASS
element.displayName: TestModel
element.documentationComment: null
element.enclosingElement: flutter_annotation|lib/demo_class.dart
element.hasAlwaysThrows: false
element.hasDeprecated: false
element.hasFactory: false
element.hasIsTest: false
element.hasLiteral: false
element.hasOverride: false
element.hasProtected: false
element.hasRequired: false
element.isPrivate: false
element.isPublic: true
element.isSynthetic: false
element.nameLength: 9
element.runtimeType: ClassElementImpl
...
複製代碼

由前文咱們知道,GeneratorForAnnotation的域僅限於class, 經過 element 只能拿到 TestModel 的類信息,那類內部的 Field 和 method 信息如何獲取呢?

關注 kind 屬性值: element.kind: CLASS,kind 標識 Element 的類型,能夠是 CLASS、FIELD、FUNCTION 等等。

對應這些類型,還有相應的 Element 子類:ClassElement、FieldElement、FunctionElement等等,因此你能夠這樣:

if(element.kind == ElementKind.CLASS){
  for (var e in ((element as ClassElement).fields)) {
    print("$e \n");
  }
  for (var e in ((element as ClassElement).methods)) {
	print("$e \n");
  }
}

輸出:
int age 
int bookNum 
fun1() → void 
fun2(int a) → void 
    
複製代碼

4.generateForAnnotatedElement 參數: annotation

註解除了標記之外,攜帶參數也是註解很重要的能力之一。註解攜帶的參數,能夠經過 annotation 獲取:

annotation.runtimeType: _DartObjectConstant
annotation.read("name"): ParamMetadata
annotation.read("id"): 2
annotation.objectValue: ParamMetadata (id = int (2); name = String ('ParamMetadata'))
複製代碼

annotation 的類型是 ConstantReader,除了提供 read 方法來獲取具體參數之外,還提供了peek方法,它們兩個的能力相同,不一樣之處在於,若是read方法讀取了不存在的參數名,會拋出異常,peek則不會,而是返回null。

5.generateForAnnotatedElement 參數: buildStep

buildStep 提供的是該次構建的輸入輸出信息:

buildStep.runtimeType: BuildStepImpl
buildStep.inputId.path: lib/demo_class.dart
buildStep.inputId.extension: .dart
buildStep.inputId.package: flutter_annotation
buildStep.inputId.uri: package:flutter_annotation/demo_class.dart
buildStep.inputId.pathSegments: [lib, demo_class.dart]
buildStep.expectedOutputs.path: lib/demo_class.g.dart
buildStep.expectedOutputs.extension: .dart
buildStep.expectedOutputs.package: flutter_annotation
複製代碼

6.模板代碼生成技巧

如今,你已經獲取了所能獲取的三個信息輸入來源,下一步則是根據這些信息來生成代碼。

如何生成代碼呢?你有如下兩個選擇:

6.1 簡單模板代碼,字符串拼接:

若是須要生成的代碼不是很複雜,則能夠直接用字符串進行拼接,好比這樣:

generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    ...
    StringBuffer codeBuffer = StringBuffer("\n");
    codeBuffer..write("class ")
      ..write(element.name)
      ..write("_APT{")
      ..writeln("\n")
      ..writeln("}");
    
    return codeBuffer.toString();
  }
複製代碼

不過通常狀況下咱們並不建議這樣作,由於這樣寫起來太容易出錯了,且不具有可讀性。

6.2 複雜模板代碼,dart 多行字符串+佔位符

dart提供了一種三引號的語法,用於多行字符串:

var str3 = """大王叫我來巡山 路口碰見了如來 """;
複製代碼

結合佔位符後,能夠實現比較清晰的模板代碼:

tempCode(String className) {
    return """ class ${className}APT { } """;
  }
  
generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    ...
    return tempCode(element.name);
  } 

複製代碼

若是參數過多的話,tempCode方法的參數能夠替換爲一個Map。

(在模板代碼中不要忘記import package哦~ 建議先在編譯器裏寫好模板代碼,編譯器靜態檢查沒有問題了,再放到三引號中修改佔位符)

若是你熟悉java-apt的話,看到這裏應該會想問,dart裏有沒有相似 javapoet 這樣的代碼庫來輔助生成代碼啊?從我的角度來講,更推薦第二種方式去生成代碼,由於它表現的足夠清晰,具備足夠高的可讀性,比起javapoet這種模式,能夠更容易的理解模板代碼意義,編寫也更加簡單。

7.配置文件字段含義

在工程根目錄下建立build.yaml 文件,用來配置Builder相關信息。

如下面配置爲例:

builders:
  test_builder:
    import: 'package:flutter_annotation/test_builder.dart'
    builder_factories: ['testBuilder']
    build_extensions: { '.dart': ['.g1.dart'] }
    required_inputs:['.dart']
    auto_apply: root_package
    build_to: source

  test_builder2:
    import: 'package:flutter_annotation/test_builder2.dart'
    builder_factories: ['testBuilder2']
    build_extensions: { '.dart': ['.g.dart'] }
    auto_apply: root_package
    runs_before: ['flutter_annotation|test_builder']
    build_to: source
複製代碼

builders 下配置你全部的builder。test_builder與 test_builder2 均是你的builder命名。

  • import 關鍵字用於導入 return Builder 的方法所在包 (必須)
  • builder_factories 填寫的是咱們 return Builder 的方法名(必須)
  • build_extensions 指定輸入擴展名到輸出擴展名的映射,好比咱們接受.dart文件的輸入,最終輸出.g.dart 文件(必須)
  • auto_apply 指定builder做用於,可選值: (可選,默認爲 none)
    • "none":除非手動配置,不然不要應用此Builder
    • "dependents":將此Builder應用於包,直接依賴於公開構建器的包。
    • "all_packages":將此Builder應用於傳遞依賴關係圖中的全部包。
    • "root_package":僅將此Builder應用於頂級包。
  • build_to 指定輸出位置,可選值: (可選,默認爲 cache)
    • "source": 輸出到其主要輸入的源碼樹上
    • "cache": 輸出到隱藏的構建緩存上
  • required_inputs 指定一個或一系列文件擴展名,表示在任何可能產生該類型輸出的Builder以後運行(可選)
  • runs_before 保證在指定的Builder以前運行

配置字段的解釋較爲拗口,這裏我只列出了經常使用的一些配置字段,還有一些不經常使用的字段能夠在 source_gen 的github主頁 查閱。

Java-APT & Dart-APT對比以及 Dart-APT 的特殊性

下面咱們將列出 Java-APT 和 Dart-APT 的主要區別,作一下對比,以此加深你的理解和提供注意事項。

1.註解定義

Java-APT: 需在定義註解時指定註解被解析時機(編碼階段、源碼階段、運行時階段),以及註解做用域(類、方法、屬性)

Dart-APT: 無需指定註解被解析時機以及註解做用域,默認 Anytime and anywhere

2.註解與註解處理器的關係

Java-APT: 一個註解處理器能夠指定多個註解進行處理

Dart-APT: 使用 source_gen 提供的默認處理器: GeneratorForAnnotation ,一個處理器只能處理一個註解。

3.註解攔截範圍

Java-APT: 每個合法使用的註解都可以被註解處理器攔截。

Dart-APT: 使用 source_gen 提供的默認處理器: GeneratorForAnnotation ,處理器只能處理 top-level級別的元素,例如直接在.dart 文件定義的Class、function、enums等等,但對於類內部Fields、functions 上使用的註解則沒法攔截。

4.註解與生成文件的關係

Java-APT: 註解和生成文件的個數並沒有直接關係,開發者自行定義

Dart-APT: 在註解處理器返回值不爲空的狀況下,一般一個輸入文件對應一個輸出文件,若是不想生成文件,只須要在Generate的方法中return null便可 。若一個輸入文件包含多個註解,每一個成功被攔截到的註解都會觸發generateForAnnotatedElement 方法的調用,屢次觸發而獲得的返回值,最終會寫入到同一個文件當中。

5.註解處理器之間的運行順序

Java-APT: 沒法直接指定多個處理器之間的執行順序

Dart-APT: 能夠指定多個處理器之間的執行順序,在配置文件build.yaml中指定key值 runs_beforerequired_inputs

6.多個註解信息合併處理

Java-APT: 註解處理器指定多個須要處理的註解後,能夠在信息採集結束後統一處理

Dart-APT: 默認一個處理器只能處理一個註解,想要合併處理需指定處理器的執行順序,先執行的註解處理器負責不一樣類型註解的信息採集(採集的數據能夠用靜態變量保存),最後執行的處理器負責處理以前保存好的數據。

第三、第4點與Java-APT很是不同,你可能還有點懵,這裏用一個栗子來講明:

栗子

假設咱們有兩個文件:

example.dart

@ParamMetadata("ClassOne", 1)
class One {
  @ParamMetadata("field1", 2)
  int age;
  @ParamMetadata("fun1", 3)
  void fun1() {}
}

@ParamMetadata("ClassTwo", 4)
class Two {
  int age;
  void fun1() {}
}
複製代碼

example1.dart

@ParamMetadata("ClassThree", 5)
class Three {
  int age;
  void fun1() {}
}

複製代碼

Generate實現以下:

class TestGenerator extends GeneratorForAnnotation<ParamMetadata> {
  @override
  generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep) {
    print("當前輸入源: ${buildStep.inputId.toString()} 被攔截到的元素: ${element.name} 註解值: ${annotation.read("name").stringValue} ${annotation.read("id").intValue}");
    return tempCode(element.name);
  }

  tempCode(String className) {
    return """ class ${className}APT { } """;
  }
}
複製代碼

執行 flutter packages pub run build_runner build

控制檯輸出信息:

當前輸入源: flutter_annotation|lib/example.dart  被攔截到的元素: One 註解值: ClassOne 1
當前輸入源: flutter_annotation|lib/example.dart  被攔截到的元素: Two 註解值: ClassTwo 4
當前輸入源: flutter_annotation|lib/example1.dart  被攔截到的元素: Three 註解值: ClassThree 5
複製代碼

生成的文件:

- lib
	- example.dart
	- example.g.dart
	- example.dart
	- example1.g.dart
複製代碼

example.g.dart

class OneAPT {}

class TwoAPT {}
複製代碼

example1.g.dart

class ThreeAPT {}
複製代碼

栗子總結

在文件 example.dart 中,咱們有兩個Class使用了註解,其中一個Class除了Class自己之外,它的field 和 function 也使用了註解。

但在輸出中,咱們只攔截到了 ClassOne, 並無被攔截到 field1 fun1。

這解釋了:

  • library.annotatedWith 遍歷的 Element 僅包括top-level級別的 Element,也就是那些文件級別的 Class、function等等,而Class 內部的 fields、functions並不在遍歷範圍,若是在 Class 內部的fields 或 functions 上修飾註解,GeneratorForAnnotation並不能攔截到!

生成的 .g.dart 文件當中,由於Class One 和 Class Two 都在文件 example.dart 中,因此生成的代碼也都拼接在了文件example.g.dart中。

這解釋了:

  • 若一個輸入文件包含多個註解,每一個成功被攔截到的註解都會觸發 generateForAnnotatedElement 方法的調用,屢次觸發而獲得的返回值,最終會寫入到同一個文件當中。

另一個文件example1.dart 則單獨生成了文件 example1.g.dart

這解釋了:

  • 當返回值不爲空的狀況下,每個文件輸入源對應着一個文件輸出。也就是說源碼中,每個*.dart文件都會觸發一次generate方法調用,若是返回值不爲空,則輸出一個文件。

Dart-APT Generator 源碼淺析

1.Generator 源碼淺析

Generator源碼炒雞炒雞簡單:

abstract class Generator {
  const Generator();

  /// Generates Dart code for an input Dart library.
  ///
  /// May create additional outputs through the `buildStep`, but the 'primary'
  /// output is Dart code returned through the Future. If there is nothing to
  /// generate for this library may return null, or a Future that resolves to
  /// null or the empty string.
  FutureOr<String> generate(LibraryReader library, BuildStep buildStep) => null;

  @override
  String toString() => runtimeType.toString();
}
複製代碼

就這麼幾行代碼,在 Builder 運行時,會調用 Generator 的 generate方法,並傳入兩個重要的參數:

  • library 經過它,咱們能夠獲取源代碼信息以及註解信息
  • buildStep 它表示構建過程當中的一個步驟,經過它,咱們能夠獲取一些文件的輸入輸出信息

值得注意的是,library 包含的源碼信息是一個個的 Element 元素,這些 Element 能夠是Class、能夠是function、enums等等。

ok,讓咱們再來看看 source_gen 中,Generator 的惟一子類 :GeneratorForAnnotation 的源碼:

abstract class GeneratorForAnnotation<T> extends Generator {
  const GeneratorForAnnotation();

  //1   typeChecker 用來作註解檢查
  TypeChecker get typeChecker => TypeChecker.fromRuntime(T);

  @override
  FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
    var values = Set<String>();

    //2  遍歷全部知足 註解 類型條件的element
    for (var annotatedElement in library.annotatedWith(typeChecker)) {
      //3 知足檢查條件的調用 generateForAnnotatedElement 執行開發者自定義的代碼生成邏輯
      var generatedValue = generateForAnnotatedElement(
          annotatedElement.element, annotatedElement.annotation, buildStep);
          //4 generatedValue是將要生成的代碼字符串,經過normalizeGeneratorOutput格式化
      await for (var value in normalizeGeneratorOutput(generatedValue)) {
        assert(value == null || (value.length == value.trim().length));
        //5 生成的代碼加入集合
        values.add(value);
      }
    }
	//6
    return values.join('\n\n');
  }
	
	//7
  generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep);
複製代碼
  • //1 : typeChecker 用來作註解檢查,效驗Element上是否修飾了目標註解
  • //2 : library.annotatedWith(typeChecker) 會遍歷全部的 Element,並經過typeChecker檢查這些Element 是否修飾了目標註解。值得再次說明的是:library.annotatedWith 遍歷的 Element 僅包括top-level級別的 Element,也就是那些文件級別的 Class、function等等,而Class 內部的 fields、functions並不在遍歷範圍,若是在 Class 內部的fields 或 functions 上修飾註解,GeneratorForAnnotation並不會攔截到!
  • //3 : 知足條件後,調用generateForAnnotatedElement方法,也就是咱們自定義Generator所實現的抽象方法。
  • //4 : generatedValue 是generateForAnnotatedElement返回值,也是咱們要生成的代碼,調用normalizeGeneratorOutput去作格式化。
  • //5 : 知足條件後,添加到集合values當中。值得再次說明的是: 以前咱們也提到過,當返回值不爲空的狀況下,每個文件輸入源對應着一個文件輸出。也就是說源碼中,每個*.dart文件都會觸發一次generate方法調用,而其中每個符合條件的目標註解使用,都會觸發一次generateForAnnotatedElement 調用,若是被屢次調用,多個返回值最終會拼接起來,輸出到一個文件當中。
  • //6 : 每一個單獨的輸出之間用兩個換行符分割,最終輸出到一個文件當中。
  • //7 : 咱們自定義Generator所實現的抽象方法。

2.library.annotatedWith 源碼淺析

GeneratorForAnnotation的源碼也很簡單,惟一值得關注的是 library.annotatedWith方法,咱們看看它的源碼:

class LibraryReader {
  final LibraryElement element;
  //1 element輸入源,這裏容易產生誤解
  LibraryReader(this.element);

  ...

  //2 全部Element,但僅限top-level級別
  Iterable<Element> get allElements sync* {
    for (var cu in element.units) {
      yield* cu.accessors;
      yield* cu.enums;
      yield* cu.functionTypeAliases;
      yield* cu.functions;
      yield* cu.mixins;
      yield* cu.topLevelVariables;
      yield* cu.types;
    }
  }

  Iterable<AnnotatedElement> annotatedWith(TypeChecker checker,
      {bool throwOnUnresolved}) sync* {
    for (final element in allElements) {
      //3 若是修飾了多個相同的註解,只會取第一個
      final annotation = checker.firstAnnotationOf(element,
          throwOnUnresolved: throwOnUnresolved);
      if (annotation != null) {
        //4 將annotation包裝成AnnotatedElement對象返回
        yield AnnotatedElement(ConstantReader(annotation), element);
      }
    }
  }

複製代碼
  • //1 : Element對象是很標準的組合模式,這裏容易產生的誤解:這個Element,是被應用的項目中,全部源代碼的的一個根 Element。這是錯誤的,正確的答案是:這個Element和其子元素,所包含的範圍,僅限一個文件。
  • //2 : 這裏的 allElements 僅限top-level級別的子Element
  • //3 : 這裏會藉助 checker 檢查 Element 所修飾的註解,若是修飾了多個相同的註解,只會取第一個,若是沒有目標註解,則返回null
  • //4 : 返回的 annotation 實際只是一個 DartObject 對象,能夠經過這個對象來取值,但爲了便於使用,這裏要將它再包裝成API更友好的AnnotatedElement,而後返回。

總結

好啦~ 到這裏你已經對 Dart-APT 有一個初步的認識了,應該具備使用 Dart-APT 的提升開發效率的能力了! APT 自己並不難,難的是利用 APT 的創意!期待你的想法與創做!

哦對了~ 全篇看下來,你應該會發現 Dart-APT 與 Java-APT 相比,它的實現仍是比較特殊的,對比 Java-APT,好多能力都暫不具有或實現起來比較繁瑣,咱們整理下哦:

  • 沒法攔截在類內部 屬性、方法上等使用的註解
  • 一個註解處理器只能處理一個註解
  • 沒有直接的API自定義文件生成等等
  • 多註解信息合併處理較爲繁瑣

另外經過閱讀 Generate 源碼,咱們還意識到有一些能力 Dart-APT 能夠實現但 Java-APT 很差實現:

  • 直接攔截某一個 Class 或 全部繼承自該 Class 的子類,而不使用註解。

Flutter仍是一個新興技術, source_gen 目前只提供了最基礎的APT能力,上面的這些功能的實現並非不能,而只是時間或ROI的問題了。

後面計劃針對這些功能,產出一個 Dart-APT 擴展庫,期待一下吧 (^__^)~

相關文章
相關標籤/搜索