Dart:通过注解生成代码
引言拒绝重复工作,追求效率和性能。基于Dart的注解处理库 source_gen ,我们来感受一下,如何使用自定义注解生成代码。
添加引用
source_gen: 用于解析注解
build_runner : 用于生成代码
dependencies:flutter: sdk: fluttersource_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 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 为需要解析的注解类,也就是我们自定义的 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 {@overridegenerateForAnnotatedElement(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.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) ...<hr>
[*]配置文件
我们知道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: sourceimport:Builder 的全局方法所在文件的具体路径
builder_factories:Builder 的全局方法的方法名称
<hr>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* members class中拦截到的成员方法: void funPrint(String* content) */}总结提示:
[*]一个 Generator 只能解析一种类型的 annotation,如果有多种类型的 annotation 需要创建多个解析器。(配置文件中也需要同步添加对应内容)
[*]使用 source_gen 提供的默认处理器: GeneratorForAnnotation ,处理器只能处理 top-level级别的元素,例如直接在.dart 文件定义的Class、function、enums等等,但对于类内部Fields、functions 上使用的注解则无法拦截。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]