反射 API 高级应用
注意:
dart:mirrors只能跑在 Dart VM 上(命令行脚本、服务端),Flutter App 内无法使用反射——做跨平台项目时要提前想好替代方案。
1. 运行时类型操作
import 'dart:mirrors';
void main() {
final mirror = reflect(MyClass());
final classMirror = mirror.type;
// 获取类的元数据
print('类名: ${classMirror.simpleName}');
print('是否为抽象类: ${classMirror.isAbstract}');
// 获取所有方法
classMirror.declarations.forEach((symbol, declarationMirror) {
if (declarationMirror is MethodMirror && !declarationMirror.isConstructor) {
print('方法: ${symbol}');
print('返回类型: ${declarationMirror.returnType.reflectedType}');
}
});
}
class MyClass {
String getName() => 'MyClass';
int calculate(int x) => x * 2;
}
2. 动态调用方法
import 'dart:mirrors';
void main() {
final instance = MyService();
final mirror = reflect(instance);
// 动态调用方法
final result = mirror.invoke(
#processData,
['test data'],
{#option: true},
);
print('调用结果: ${result.reflectee}');
}
class MyService {
String processData(String data, {bool option = false}) {
return option ? data.toUpperCase() : data.toLowerCase();
}
}
代码生成工具链
1. 构建器配置
## build.yaml
targets:
$default:
builders:
my_builder:
enabled: true
generate_for:
- lib/**/*.dart
2. 自定义构建器
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:analyzer/dart/element/element.dart';
class MyBuilder extends Builder {
@override
Map<String, List<String>> get buildExtensions => {
'.dart': ['.g.dart'],
};
@override
Future<void> build(BuildStep buildStep) async {
final inputId = buildStep.inputId;
final content = await buildStep.readAsString(inputId);
// 生成代码逻辑
final generatedCode = generateCode(content);
// 写入生成的文件
final outputId = inputId.changeExtension('.g.dart');
await buildStep.writeAsString(outputId, generatedCode);
}
}
String generateCode(String source) {
// 实现代码生成逻辑
return '''// 生成的代码
// ...
''';
}
3. 注解处理器
// 定义注解
class GenerateJson {
const GenerateJson();
}
// 注解处理器
class JsonGenerator extends GeneratorForAnnotation<GenerateJson> {
@override
String generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) {
if (element is! ClassElement) {
throw InvalidGenerationSourceError(
'只能在类上使用 @GenerateJson 注解',
element: element,
);
}
return generateJsonCode(element);
}
}
String generateJsonCode(ClassElement classElement) {
final className = classElement.name;
final buffer = StringBuffer();
// 生成可从类外调用的顶层函数,并把实例显式作为参数传入。
buffer.writeln(
"Map<String, dynamic> _\$${className}ToJson($className instance) => {",
);
for (final field in classElement.fields) {
if (field.isStatic || field.isPrivate || field.isSynthetic) continue;
buffer.writeln(" '${field.name}': instance.${field.name},");
}
buffer.writeln('};');
return buffer.toString();
}
这个片段只演示“生成的顶层函数通过 instance 访问公开实例字段”的作用域关系。它没有覆盖重命名、继承、私有字段、默认值、泛型和类型转换;真实 JSON 生成器需要逐项定义这些规则,并在 generator 工程中分析生成文件。
元数据处理
1. 自定义注解
// 定义注解
class Validate {
final String pattern;
final String message;
const Validate(this.pattern, {this.message = ''});
}
// 使用注解
class User {
@Validate(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$', message: '无效的邮箱格式')
String email;
@Validate(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$', message: '密码至少8位,包含字母和数字')
String password;
User(this.email, this.password);
}
2. 注解处理
import 'dart:mirrors';
class ValidationProcessor {
static List<String> validate(Object instance) {
final errors = <String>[];
final mirror = reflect(instance);
final classMirror = mirror.type;
classMirror.declarations.forEach((symbol, declarationMirror) {
if (declarationMirror is VariableMirror) {
final annotations = declarationMirror.metadata;
for (final annotation in annotations) {
if (annotation.reflectee is Validate) {
final validate = annotation.reflectee as Validate;
final value = mirror.getField(symbol).reflectee as String;
if (!RegExp(validate.pattern).hasMatch(value)) {
errors.add(validate.message.isNotEmpty
? validate.message
: '${symbol} 验证失败');
}
}
}
}
});
return errors;
}
}
代码生成最佳实践
1. 增量构建与缓存
build_runner 管理构建步骤和增量输入;不要在 Builder 内遇到缓存命中就直接 return 而不写输出文件,否则当前构建可能缺少生成产物。缓存行为应遵循 build 系统的输入/输出模型,并用连续两次构建验证结果一致。
2. 错误处理
class ErrorHandlingBuilder extends Builder {
@override
Map<String, List<String>> get buildExtensions => {
'.dart': ['.g.dart'],
};
@override
Future<void> build(BuildStep buildStep) async {
try {
// 构建逻辑
} catch (e, stackTrace) {
// 注意:package:build 里没有顶层 log,这里直接打印,信息里带上文件路径方便定位
print('构建失败: $e\n文件: ${buildStep.inputId.path}\n$stackTrace');
// 提供有用的错误信息
throw BuilderError(
'构建失败: ${e.toString()}\n'
'文件: ${buildStep.inputId.path}\n'
'请检查源代码或报告问题',
);
}
}
}
class BuilderError extends Error {
final String message;
BuilderError(this.message);
@override
String toString() => message;
}
调试技巧
1. 日志记录
class DebugBuilder extends Builder {
static const _debug = true;
@override
Map<String, List<String>> get buildExtensions => {
'.dart': ['.g.dart'],
};
void debugLog(String message) {
if (_debug) {
print('[DEBUG] $message');
}
}
@override
Future<void> build(BuildStep buildStep) async {
debugLog('开始处理: ${buildStep.inputId.path}');
// 构建逻辑
debugLog('处理完成');
}
}
2. 代码检查
代码片段的语法检查和 lint 是两件事。对于真正的 generator 工程,应把生成产物写到 build 输出中,再运行 dart analyze 或 generator 项目配置的分析步骤;不要用一个未配置规则的 Linter([]) 代替完整分析。
实战案例:我绕开了整个元编程工具链
本文讲的都是”正统”元编程:build_runner、source_gen、注解处理器。但我的 ygking_json_to_dart_model 项目(143 行,零第三方代码生成依赖)走的是另一条路——运行时字符串模板。
对比一下两种路线:
| build_runner 路线 | 我的字符串模板路线 | |
|---|---|---|
| 时机 | 编译期 | 运行期(用户点”生成”按钮时) |
| 输入 | 已写好的 Dart 类 + 注解 | 一段 JSON 文本 |
| 输出 | .g.dart 文件 | 直接显示在预览区的字符串 |
| 依赖 | build_runner + source_gen + 注解包 | 零依赖,dart:convert 就够 |
选字符串模板不是因为不懂 build_runner,而是场景根本套不上:build_runner 是给”工程代码”做生成的,我的场景是”用户扔一段 JSON 进来,立刻要一份 Dart 类”。为这个去配 build.yaml、写 Builder 子类,是大炮打蚊子。
但字符串模板有个硬伤我得承认:零类型检查。buffer.writeln(' $type? $key;') 拼出来的代码,Dart 编译器在”生成”那一刻是看不见的——拼错了要等用户把结果复制到工程里编译才发现。所以我定了条死规矩:生成器自己的单元测试必须覆盖”生成的代码能通过 dart analyze”(项目里有 test/ 目录就是干这个的)。
一句话总结:元编程的本质是”让程序写程序”,build_runner 只是其中一种实现。当你的”程序”简单到 143 行字符串拼接就能搞定时,别为了”正统”而上工具链。
总结
Dart 元编程包含运行时反射、静态代码分析、注解和构建期代码生成等不同机制。选择方案时要先确认运行平台、代码生成发生的时机,以及生成物能否被分析和测试。
真实 generator 工程应固定 Dart、analyzer、build_runner 与 source_gen 版本;示例片段不能代替完整工程的 dart analyze 和生成物检查。
