美文网首页
Dart:通过注解生成代码

Dart:通过注解生成代码

作者: 李小轰 | 来源:发表于2021-07-15 16:15 被阅读0次
引言

拒绝重复工作,追求效率和性能。基于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);
}
  • 第三步:解析注解,生成代码
  1. 创建解析器需要继承 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) 
...

  1. 配置文件

我们知道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


image.png

根据解析器拦截逻辑,生成的代码为:

// GENERATED CODE - DO NOT MODIFY BY HAND

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

class ClassModelAPT {
  /**
          class中拦截到的成员字段有:
   String* className
   List<Student*>* members

 class中拦截到的成员方法:
   void funPrint(String* content)

        */
}

总结提示:
  1. 一个 Generator 只能解析一种类型的 annotation,如果有多种类型的 annotation 需要创建多个解析器。(配置文件中也需要同步添加对应内容)

  2. 使用 source_gen 提供的默认处理器: GeneratorForAnnotation ,处理器只能处理 top-level级别的元素,例如直接在.dart 文件定义的Class、function、enums等等,但对于类内部Fields、functions 上使用的注解则无法拦截。

相关文章

网友评论

      本文标题:Dart:通过注解生成代码

      本文链接:https://www.haomeiwen.com/subject/ndzppltx.html