自定义生成器 (Generator)
Builder 负责文件系统的操作(读取 .dart 文件,创建 .g.dart 文件),其逻辑比较底层,写起来稍显繁琐。Generator 封装了 Builder,让我们只需关注“输入一个类,输出一段字符串”的逻辑。绝大多数场景下,我们只需自定义 Generator。
示例
假设我们要为 OMDb 模型类(如 Movie 类)自动生成一个字段清单字符串。
a. Generator 由 source_gen 包提供。首先添加如下依赖包:
dart pub add source_gen analyzer build
b. 定义注解(OMDbModelInfo).
// lib/codegen/annotations.dart
class OMDbModelInfo {
const OMDbModelInfo();
}
c. 定义基于注解(OMDbModelInfo)的Generator(ModelInfoGenerator)。
// lib/codegen/model_info_generator.dart
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'annotations.dart';
class ModelInfoGenerator extends GeneratorForAnnotation<OMDbModelInfo> {
@override
String generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
log.info('>>>>>>>> 正在处理类: ${element.name} <<<<<<<<');
// 确保注解是在类上
if (element is! ClassElement) throw 'Only classes!';
final className = element.name;
// 获取所有字段名
final fields = element.fields.map((f) => f.name).join(', ');
// 返回的字符串会直接写入到 .g.dart 中
return '''
// ignore: unused_element
const _\$${className}Fields = "$fields";
''';
}
}
d. 创建一个全局函数(omdbModelBuilder),该函数返回一个 Builder。
// lib/codegen/builder.dart
import 'package:build/build.dart';
import 'package:hellodart/codegen/model_info_generator.dart';
import 'package:source_gen/source_gen.dart';
Builder omdbModelBuilder(BuilderOptions options) =>
PartBuilder([ModelInfoGenerator()], '.info.g.dart');
这里指定生成文件的后缀:.info.g.dart 。
e. 配置 build.yaml。
builders:
omdb_model_builder:
import: "package:hellodart/codegen/builder.dart"
# 指向一个返回 Builder 的顶层函数
builder_factories: ["omdbModelBuilder"]
# 规定:输入 .dart 文件,输出 .info.g.dart 文件
build_extensions: {".dart": [".info.g.dart"]}
auto_apply: dependents
build_to: source
# targets 用于定义构建范围和具体插件的行为
targets:
$default:
builders:
hellodart|omdb_model_builder:
generate_for:
- bin/ch09/*.dart
- 根部的 builders 是声明(Declaration),targets 里的 builders 是引用(Reference)。
hellodart|omdb_model_builder: 这里的语法是packageName|builderName。如果packageName与builderName相同,可以只写builderName。上一节中的json_serializable的全名是json_serializable|json_serializable。这种设计体现了 Dart 构建系统的模块化哲学。
f. 生成代码.
修改 movie.dart:
part 'movie.g.dart';
part 'movie.info.g.dart';
@OMDbModelInfo()
@JsonSerializable()
class Movie {
// ... (omited for brevity)
}
然后运行 build 命令:
dart run build_runner build
Builder 类层次
从 Builder的类图可以看出 Builder 与 Generator 的关系。
classDiagram
class Builder {
<<abstract>>
+Map buildExtensions
+build(BuildStep buildStep)*
}
class PartBuilder {
+List generators
+String formatOutput
+build(BuildStep buildStep)
}
class SharedPartBuilder {
+List generators
+String partId
+build(BuildStep buildStep)
}
class LibraryBuilder {
+Generator generator
+build(BuildStep buildStep)
}
class Generator {
<<abstract>>
+generate(LibraryReader library, BuildStep buildStep)*
}
class GeneratorForAnnotation~T~ {
<<abstract>>
+generateForAnnotatedElement(Element element, ConstantReader annotation, BuildStep buildStep)*
}
%% 关系
Builder <|-- PartBuilder : 继承 (独立生成文件)
Builder <|-- SharedPartBuilder : 继承 (生成到 .g.dart)
Builder <|-- LibraryBuilder : 继承 (生成整个库)
PartBuilder o-- Generator : 持有 (1..*)
SharedPartBuilder o-- Generator : 持有 (1..*)
LibraryBuilder o-- Generator : 持有 (1)
Generator <|-- GeneratorForAnnotation : 继承 (基于注解触发)
ConstantReader
当我们写下 @JsonSerializable(explicitToJson: true) 时,explicitToJson 这个布尔值是如何传递给Generator的?这就是 ConstantReader 的工作,它负责将源码中的“静态常量”转换为 Generator 可以直接操作的 Dart 对象。
继续之前的示例,设想我们要为 OMDb模型类自动生成一个扩展方法 printFields,该方法打印模型类的所有字段。我们向 OMDbModelInfo 注解添加一个布尔开关 ext,只有当开发者明确声明 ext: true 时,Generator 才生成 printFields。
首先修改 OMDbModelInfo,添加 ext 属性.
// lib/codegen/annotations.dart
class OMDbModelInfo {
final bool ext;
const OMDbModelInfo({this.ext = false});
}
然后我们来看看如何读区 ext 属性。创建一个新的 Generator (ModelExtGenerator)。
// lib/codegen/model_ext_generator.dart
// import ... (omited for brevity)
class ModelExtGenerator extends GeneratorForAnnotation<OMDbModelInfo> {
@override
String generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
if (element is! ClassElement) throw 'Only classes!';
final ext = annotation.read('ext').boolValue;
if (!ext) return "";
return "// TODO";
}
}
annotation.read('ext') 读取 ext属性,如果该属性不存在将报错。还可以使用 peek('prop')读取属性,这种方式具有更高的容错性 — 如果属性不存在,它将返回 null,适用于可选属性(配置)。
final ext = annotation.peek('ext')?.boolValue ?? false;
code_builder
在之前的 ModelInfoGenerator 实战中,我们使用了简单的字符串拼接(return 'const ...')。但在面对复杂的类结构、导入管理、私有字段时,这简直是开发者的噩梦。而 code_builder 具有如下 自动导入、代码格式化、语义化编程等特性,极大地简化了 复杂Generator 的编写。字符串拼接如同剪纸拼图,琐碎且易错;而 code_builder 则是语义化的乐高,它用对象化的组件替代了原始的文本堆砌,让代码生成变得严谨且优雅
接下来使用 code_builder 来实现 ModelExtGenerator的具体逻辑,以演示其用法。
// lib/codegen/model_ext_generator.dart
// import ... (omited for brevity)
class ModelExtGenerator extends GeneratorForAnnotation<OMDbModelInfo> {
@override
String generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
if (element is! ClassElement) throw 'Only classes!';
final ext = annotation.read('ext').boolValue;
if (!ext) return "";
final className = element.name!;
// 使用 code_builder 构建代码对象
final extension = Extension(
(e) => e
..name = '${className}Ext'
// 绑定到原类
..on = refer(className)
..methods.add(
Method(
(m) => m
..name = 'printFields'
..returns = refer('void')
..body = Block.of([
// 自动生成打印逻辑
refer(
'print',
).call([refer('_\$${className}Fields')]).statement,
]),
),
),
);
// 使用 DartEmitter 将对象发射(Emit)为字符串
final emitter = DartEmitter();
return '${extension.accept(emitter)}';
}
}
修改 omdbModelBuilder 全局函数:
Builder omdbModelBuilder(BuilderOptions options) =>
PartBuilder([ModelInfoGenerator(), ModelExtGenerator()], '.info.g.dart');
最后,在 Movie 类上使用 @OMDbModelInfo(ext: true) ,它会自动生成:
extension MovieExt on Movie {
void printFields() {
print(_$MovieFields);
}
}