引言
拒绝重复工作,追求效率和性能。基于Dart的注解处理库 source_gen ,我们来感受一下,如何使用自定义注解生成代码。
添加引用
source_gen : 用于解析注解
build_runner : 用于生成代码
dependencies:
flutter:
sdk: flutter
source_gen:
build_runner:
-
第一步:自定义annotation
新建 test_anotaion.dart 文件,自定义带入参的类 ParamMetadata 用作注解使用
//带入参的自定义注解
class ParamMetadata {
final String name;
final int id;
const ParamMetadata(this.name, this.id);
}
重点: 注解类其构造方法要求必须使用const修饰
-
第二步:使用注解
注解类定义好后,新建文件 model_class.dart,创建一个实体类,并使用 ParamMetadata
进行注解标识:
@ParamMetadata("annotationClass", 1)
class ClassModel {
final String className;
final List<Student> members;
ClassModel(this.className, this.members);
void funPrint(String content) {
print("className = $className ; members = ${members.toString()}");
}
}
class Student {
final String name;
const Student(this.name);
}
-
第三步:解析注解,生成代码
- 创建解析器需要继承 GeneratorForAnnotation<T> 泛型T 为需要解析的注解类,也就是我们自定义的 ParamMetadata。
import 'package:source_gen/source_gen.dart';
import 'package:test_anotation/anotation/test_anotation.dart';
import 'package:analyzer/dart/element/element.dart' as e;
import 'package:build/build.dart';
import 'package:test_anotation/generator/temp_code.dart';
class TestGenerator extends GeneratorForAnnotation<ParamMetadata> {
@override
generateForAnnotatedElement(e.Element element, ConstantReader annotation, BuildStep buildStep) {
analyseBuildStep(buildStep);
analyseAnnotation(annotation);
return tempCode(element.name, analyseElement(element));
}
//分析元素
String analyseElement(e.Element element) {
print("ElementKind : ${element.kind.name} \n");
switch (element.kind) {
case e.ElementKind.CLASS:
//注释用在class上
return _analyseElementForClass(element as e.ClassElement);
case e.ElementKind.FUNCTION:
//注解用在方法上
return _analyseElementForMethod(element as e.FunctionElement);
default:
return "";
}
}
//注解用在class上
String _analyseElementForClass(e.ClassElement classElement) {
var fieldStr = " class中拦截到的成员字段有:\n";
for (var e in classElement.fields) {
fieldStr += " ${e.declaration}\n";
}
var methodStr = " class中拦截到的成员方法:\n";
for (var e in classElement.methods) {
methodStr += " ${e.declaration}\n";
}
return fieldStr + "\n" + methodStr;
}
//注解用在方法上
String _analyseElementForMethod(e.FunctionElement methodElement) {
var result =
"方法名称 : ${methodElement.name}, 方法参数:${methodElement.parameters[0].declaration} \n";
return result;
}
//分析注解传参
void analyseAnnotation(ConstantReader annotation) {
print("analyseAnnotation \n");
print("params - name : ${annotation.read("name")}\n");
print("params - id : ${annotation.read("id")}\n");
}
//分析构建的输入输出信息
void analyseBuildStep(BuildStep buildStep) {
print("当前输入源: ${buildStep.inputId.toString()}\n");
}
}
//创建文件的模板
tempCode(String className, String content) {
return """
class ${className}APT {
/**
$content
*/
}
""";
}
重写 generateForAnnotatedElement 对 ParamMetadata 类型的注解进行拦截:
-
analyseElement()
: 获取分析元素,区分元素类型分别处理,具体看代码注解。demo中把解析出来的内容封存成字符串,最终把文本结果显示在生成的代码文件上。 -
analyseBuildStep()
:获取构建的输入输出信息,例如demo中会打印
当前输入源: test_anotation|lib/model/model_class.dart -
analyseAnnotation()
:获取分析注解的传参,例如我们注解构造方法里的 name , id 传参字段
有小伙伴应该注意到了 import 引用中使用了 as
关键字,在使用 Element 时,直接使用别名进行引用,这是由于 Element 在不同的库里有重复命名,使用 as
关键字处理报错报红。
import 'package:analyzer/dart/element/element.dart' as e;
...
generateForAnnotatedElement(e.Element element, ConstantReader annotation, BuildStep buildStep)
...
-
配置文件
我们知道Flutter中在禁用了dart:mirror,无法使用反射。所以我们只能通过命令在编译期进行触发操作,在执行命令前,我们还有一个步骤需要实现,创建配置文件:
- 创建一个返回类型为 Builder 的全局方法:新建一个文件(名字随意),例如 test_build.dart,内容如下:
import 'package:source_gen/source_gen.dart';
import 'package:build/build.dart' as build;
import 'package:test_anotation/generator/annotation_class_generator.dart';
build.Builder testBuilder(build.BuilderOptions options) => LibraryBuilder(TestGenerator());
// TestGenerator 就是我们的解析器
- 在项目根目录创建 build.yaml 文件,其意义在于 配置 Builder 的各项参数:
builders:
testBuilder:
import: "package:test_anotation/builder/test_builder.dart"
builder_factories: ["testBuilder"]
build_extensions: {".dart": [".g.part"]}
auto_apply: root_package
build_to: source
import:Builder 的全局方法所在文件的具体路径
builder_factories:Builder 的全局方法的方法名称
build Run 运行,(解析注解生成代码)
完成以上工作后,可直接在项目的命令行窗口,运行如下命令,开始解析注解,生成代码:
清理生成的文件:
flutter packages pub run build_runner clean
生成代码文件:
flutter packages pub run build_runner build
根据 model_class.dart 文件,同目录下生成文件 model_class.g.dart

根据解析器拦截逻辑,生成的代码为:
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// TestGenerator
// **************************************************************************
class ClassModelAPT {
/**
class中拦截到的成员字段有:
String* className
List<Student*>* members
class中拦截到的成员方法:
void funPrint(String* content)
*/
}
总结提示:
-
一个 Generator 只能解析一种类型的 annotation,如果有多种类型的 annotation 需要创建多个解析器。(配置文件中也需要同步添加对应内容)
-
使用 source_gen 提供的默认处理器: GeneratorForAnnotation ,处理器只能处理 top-level级别的元素,例如直接在.dart 文件定义的Class、function、enums等等,但对于类内部Fields、functions 上使用的注解则无法拦截。
网友评论