Dart 的自动代码构建---基于source_gen和build_runner实现

甄煜
2023-12-01

1. 前言

Dart 和Java一样 能够在代码中写注解,也能够反射或者用其他方式获得注解内容,当然也能够基于注解 动态生成一些代码
同样目前比较厉害的第三方库 json_serializable 就是基于 source_gen 和 build_runner 实现 json 注解 完成 json字符串和对象互转的

2. 实现

1 创建library

首先需要一个独立的library项目来完成注解生成代码的builder,同时创建相关的注解类 ,在 pubspec.yaml 中 依赖 build_runner 和 source_gen

2 构造 build_runner 和 build.yaml

build_runner 需要 build.yaml声明依赖的builder构造器build.yaml 放在 pubspec.yaml 一起就好
简单描述下这个 build.yaml,申明了一个 api_generator构造器,会基于.dart文件生成 .g.part文件
这个Builder会在 package:request_generator/builder.dart 的 apiBuilder 方法返回

build.yaml如下

targets:
  $default:
    builders:
      api_generator:
        enabled: true

builders:
  api_generator:
    import: "package:request_generator/builder.dart"
    builder_factories: ["apiBuilder"]
    build_extensions: {".dart": ["api_generator.g.part"]}
    auto_apply: all_packages
    build_to: cache
    applies_builders: ["source_gen|combining_builder"]
    

3 builder构建

实现这个builder ,就是实现如下方法

Builder apiBuilder(BuilderOptions options) {
  print("xxxx");
  return SharedPartBuilder([ApiBuilderGenerator()], "api_generator");
}

SharedPartBuilder 是 source_gen提供的用于生成 .g.part的类,然后我们需要实现
里面用到的 ApiBuilderGenerator,这个类是继承自
source_gen提供的GeneratorForAnnotation 类, generateForAnnotatedElement 方法返回的字符串就会自动生成在文件中,简单的来说,这里用了注解类
Request,每个注解为 Request的Dart 类就会进入这个注解扫描生成器 GeneratorForAnnotation,转为 ClassElement对象,然后通过遍历ClassElement的 methods 取得每个方法 methodElement ,每个methodElement 里面有个metadata 集合,获取metadata集合,通过 computeConstantValue 可以获取对应的值,然后基于这些值做相应的文本拼接

class ApiBuilderGenerator extends GeneratorForAnnotation<Request> {
  @override
  generateForAnnotatedElement(
      Element element, ConstantReader annotation, BuildStep buildStep) {
    print("element is $element");
    if (element is! ClassElement) {
      throw InvalidGenerationSourceError(
          "Request class is not ok for ${element.displayName}");
    }
    StringBuffer stringBuffer=StringBuffer("");
    for (var methodElement in (element as ClassElement).methods) {
      for (var annometadata in methodElement.metadata) {
        final metadata = annometadata.computeConstantValue();
        final metadatatype = annometadata.runtimeType;
        print("metadatatype is $metadatatype");
        print("metadata type is ${metadata.type.runtimeType}");
        if (metadata.type.name == "ApiMethod") {
          String method = metadata.getField("method").toStringValue();
          String url = metadata.getField("url").toStringValue();
          var headerfield = metadata.getField("head");
          var head = {};
          if (headerfield != null) {
            print("headerfield:${headerfield.toMapValue()}");
            head = headerfield.toMapValue().map((key, value) =>
                MapEntry(key.toStringValue(), value.toStringValue()));
          }
          if (head == null) {
            head = {};
          }
          head["Content-Type"] = "application/json";
          print("genertor is ${method}");

          print("bool is ${method == "get"}");

          if ("get" == method) {
            print("get");
            stringBuffer.write( _genGetAnnotation(url, methodElement, head));
            stringBuffer.writeln();

          } else if ("post" == method) {
            print("post");
            stringBuffer.write( _genPostAnnotation(url, methodElement, head));
            stringBuffer.writeln();
          }
        }
      }
    }
    return stringBuffer.toString();
  }

  String _genGetAnnotation(String url, MethodElement element, Map head) {
    return """
       Future _\$get_${element.name}() {
        ${_getHeadMapString(head)}
        return http.get("$url",headers:header);
       
       }
      """;
  }

  String _genPostAnnotation(String url, MethodElement element, Map head) {
    return """
       Future _\$post_${element.name}() {
        ${_getHeadMapString(head)}
        return http.post("${url}",headers:header);
       }
      """;
  }

  String _getHeadMapString(Map head) {
    return """
        var header =${jsonEncode(head)};
        """;
  }
}
  • 4 . Request注解类 和导出
    实现的注解类如下
 class Request {
   const Request();
}


class ApiMethod{
  final String url;
  final String method;
  final Map head;

  const ApiMethod(this.url, this.method,{this.head});
}

class HttpRequestType {
  static const String GET = "get";
  static const String POST = "post";
}

4 导出注解

按照业界规范,放到 lib/src目录中,同时在lib中创建一个dart文件,将这些类导出

library request_generator;
export 'src/request.dart';

5 使用注解

最后就是在别的library或者project中使用这些注解了
具体如下

import 'package:request_generator/out_generator.dart';
import 'package:http/http.dart' as http;
part 'request_util.g.dart';

@Request()
 class RequestUtil {
  static RequestUtil sInstance = new RequestUtil._internal();

  RequestUtil._internal();

  factory RequestUtil() {
    return sInstance;
  }
  @ApiMethod("https://swapi.co/api/starships",HttpRequestType.GET,head: {"token":"123"})
  Future  ships(){
    return _$get_ships();
  }

  @ApiMethod("https://swapi.co/api/starships",HttpRequestType.POST,head: {"token":"123"})
  Future  upships(){
    return _$post_upships();
  }

}

6 运行脚本

最后执行命令生成 .g.part文件了

flutter packages pub run build_runner build
 类似资料: