Dart: Dart 元编程与反射 — 注解、代码生成与反射机制
元编程是写代码的代码 — 自动化重复劳动,让开发者专注于业务逻辑。
1. 你将学到
- dart:mirrors 反射 API(VM only 限制)
- 注解(Annotations):定义与读取
- 代码生成 vs 反射的取舍
- reflectionless 设计哲学与 Flutter 的选择
- Bob 场景:DataPipeline 中注解驱动的字段映射
2. 一个开发者的真实故事
(1) 痛点:手动序列化代码占 60% 开发量
Bob 的 DataPipeline 有 20 个数据模型类,每个都需要 fromJson/toJson 方法。手动编写 20 个类的序列化代码花了 3 天,而且经常出错 — 一次字段名拼错导致 100,000 条记录的解析失败。
(2) 代码生成的解法
Dart 选择代码生成而非反射,通过注解标注模型类,build_runner 自动生成序列化代码。
import 'package:json_annotation/json_annotation.dart';
part 'order.g.dart';
@JsonSerializable()
class Order {
final String id;
final double amount;
Order({required this.id, required this.amount});
factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
Map<String, dynamic> toJson() => _$OrderToJson(this);
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
(3) 收益
- 20 个类的序列化代码自动生成,从 3 天降到 30 分钟
- 字段名拼错在编译时就能发现
- AOT 编译友好,Flutter/Web 全平台支持
3. 注解
(1) 注解定义与使用
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:自定义注解
// Define a custom annotation
class Column {
final String name;
final bool nullable;
final String? defaultValue;
const Column({
required this.name,
this.nullable = false,
this.defaultValue,
});
}
class Table {
final String name;
const Table(this.name);
}
// Apply annotations to a class
@Table('orders')
class Order {
@Column(name: 'order_id')
final String id;
@Column(name: 'amount', nullable: false)
final double amount;
@Column(name: 'status', defaultValue: 'pending')
final String status;
Order({required this.id, required this.amount, this.status = 'pending'});
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:使用反射读取注解(VM only)
import 'dart:mirrors';
// Read annotations using reflection (VM only!)
void printTableInfo(Type type) {
final classMirror = reflectClass(type);
// Read class-level annotations
for (final metadata in classMirror.metadata) {
if (metadata.reflectee is Table) {
final table = metadata.reflectee as Table;
print('Table: ${table.name}');
}
}
// Read field-level annotations
classMirror.declarations.forEach((key, declaration) {
if (declaration is VariableMirror) {
for (final metadata in declaration.metadata) {
if (metadata.reflectee is Column) {
final column = metadata.reflectee as Column;
print(' ${declaration.simpleName}: ${column.name} '
'(nullable: ${column.nullable}, default: ${column.defaultValue})');
}
}
}
});
}
void main() {
printTableInfo(Order);
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
4. dart:mirrors 反射 API
(1) 反射能力概览
graph TD A[Metaprogramming] --> B[dart:mirrors Reflection] A --> C[Annotations + Code Generation] B --> B1[Runtime flexible] B --> B2[VM only / AOT unavailable] C --> C1[Compile-time generation] C --> C2[AOT compatible / Flutter friendly]
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:反射 API 用法
import 'dart:mirrors';
class Product {
final String name;
final double price;
String category;
Product({required this.name, required this.price, this.category = 'General'});
String formatPrice() => '\$${price.toStringAsFixed(2)} USD';
double applyDiscount(double rate) => price * (1 - rate);
}
void reflectOnProduct() {
final mirror = reflectClass(Product);
// List all instance methods
print('Methods:');
mirror.instanceMembers.forEach((name, member) {
if (member is MethodMirror && !member.isConstructor && !member.isStatic) {
print(' $name: ${member.returnType.reflectedType}');
}
});
// Create instance via reflection
final instance = mirror.newInstance(
Symbol(''),
[],
{#name: 'Laptop', #price: 1299.99, #category: 'Electronics'},
);
// Invoke method via reflection
final formatted = instance.invoke(#formatPrice, []);
print('Formatted: ${formatted.reflectee}');
final discounted = instance.invoke(#applyDiscount, [0.1]);
print('Discounted: \$${discounted.reflectee.toStringAsFixed(2)} USD');
}
void main() {
reflectOnProduct();
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 反射能力 | API | 说明 |
|---|---|---|
| 获取类信息 | reflectClass(Type) |
类名、方法、字段 |
| 创建实例 | newInstance() |
动态实例化 |
| 调用方法 | invoke() |
动态方法调用 |
| 读取字段 | getField() |
动态属性访问 |
| 读取注解 | .metadata |
获取元数据 |
5. 代码生成 vs 反射
(1) 对比与取舍
| 维度 | dart:mirrors 反射 | 代码生成(build_runner) |
|---|---|---|
| 运行时 | 灵活,运行时决策 | 编译时确定 |
| AOT 兼容 | 不兼容 | 兼容 |
| Flutter 支持 | Release 模式不可用 | 全模式支持 |
| Web 支持 | 不可用 | 支持 |
| 性能 | 运行时开销 | 零运行时开销 |
| 开发体验 | 无需生成步骤 | 需要 build_runner 步骤 |
| 调试 | 困难(动态分派) | 简单(生成代码可读) |
6. 注解驱动的字段映射
(1) Bob 场景:DataPipeline 字段映射
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:手动注解处理器(模拟代码生成)
// Custom annotations for field mapping
class FieldMapping {
final String csvColumn;
final String? defaultValue;
final bool required;
const FieldMapping({
required this.csvColumn,
this.defaultValue,
this.required = true,
});
}
class ModelMapping {
final String tableName;
const ModelMapping(this.tableName);
}
// Model with field mapping annotations
@ModelMapping('customers')
class Customer {
@FieldMapping(csvColumn: 'customer_id')
final String id;
@FieldMapping(csvColumn: 'customer_name', defaultValue: 'Unknown')
final String name;
@FieldMapping(csvColumn: 'email', required: false)
final String? email;
@FieldMapping(csvColumn: 'total_spent', defaultValue: '0')
final double totalSpent;
Customer({
required this.id,
required this.name,
this.email,
this.totalSpent = 0,
});
// Manual mapping (in real project, this would be generated)
static Customer fromCsvMap(Map<String, String> csvRow) {
return Customer(
id: csvRow['customer_id'] ?? '',
name: csvRow['customer_name'] ?? 'Unknown',
email: csvRow['email'],
totalSpent: double.tryParse(csvRow['total_spent'] ?? '0') ?? 0,
);
}
}
void main() {
final csvRow = {
'customer_id': 'CUST-001',
'customer_name': 'Alice',
'email': 'alice@example.com',
'total_spent': '52500.75',
};
final customer = Customer.fromCsvMap(csvRow);
print('Customer: ${customer.id}, ${customer.name}');
print('Email: ${customer.email ?? "N/A"}');
print('Total: \$${customer.totalSpent.toStringAsFixed(2)} USD');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
7. reflectionless 设计哲学
(1) 为什么 Dart 选择无反射
graph TD A[Reflection vs Code Generation] --> B[Reflection] A --> C[Code Generation] B --> B1[Runtime flexibility] B --> B2[AOT incompatible] B --> B3[Flutter/Web blocked] B --> B4[Performance overhead] C --> C1[Compile-time certainty] C --> C2[AOT compatible] C --> C3[Flutter/Web friendly] C --> C4[Zero runtime cost]
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
| 设计原则 | 说明 |
|---|---|
| AOT 优先 | Flutter release 使用 AOT 编译,反射不可用 |
| Tree shaking | 编译器删除未使用代码,反射阻止 tree shaking |
| 性能优先 | 反射有运行时开销,代码生成零开销 |
| 类型安全 | 代码生成保持类型安全,反射丧失类型检查 |
▶ 示例
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
:代码生成替代反射
// Instead of reflection-based JSON parsing:
// dynamic parseJson(Map<String, dynamic> json, Type type) { ... } // BAD
// Use code generation-based approach:
// 1. Define model with annotation
// @JsonSerializable()
// class Order { ... }
// 2. Run: dart run build_runner build
// 3. Generated code provides type-safe parsing:
// factory Order.fromJson(Map<String, dynamic> json) => _$OrderFromJson(json);
// Manual version (simulating generated code)
class Order {
final String id;
final double amount;
Order({required this.id, required this.amount});
// "Generated" fromJson
factory Order.fromJson(Map<String, dynamic> json) => Order(
id: json['id'] as String,
amount: (json['amount'] as num).toDouble(),
);
// "Generated" toJson
Map<String, dynamic> toJson() => {
'id': id,
'amount': amount,
};
}
void main() {
final json = {'id': 'ORD-001', 'amount': 1500.0};
final order = Order.fromJson(json); // Type-safe!
print(order.toJson());
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
8. 完整示例:DataPipeline 注解驱动映射
// ============================================
// DataPipeline Annotation-Driven Mapping
// Simulates code generation for CSV-to-Model
// ============================================
// Annotations
class CsvField {
final String column;
final String? defaultValue;
final bool required;
const CsvField({
required this.column,
this.defaultValue,
this.required = true,
});
}
class CsvModel {
final String fileName;
const CsvModel(this.fileName);
}
// Model classes
@CsvModel('orders')
class Order {
@CsvField(column: 'order_id')
final String id;
@CsvField(column: 'total_amount', defaultValue: '0')
final double amount;
@CsvField(column: 'order_status', defaultValue: 'pending')
final String status;
@CsvField(column: 'category', required: false)
final String? category;
Order({
required this.id,
required this.amount,
this.status = 'pending',
this.category,
});
// In real project, this would be generated by build_runner
static Order fromCsvRow(Map<String, String> row) => Order(
id: row['order_id'] ?? '',
amount: double.tryParse(row['total_amount'] ?? '0') ?? 0,
status: row['order_status'] ?? 'pending',
category: row['category'],
);
double get tax => amount * 0.08;
double get total => amount + tax;
@override
String toString() => 'Order($id, \$${amount.toStringAsFixed(2)}, $status${category != null ? ", $category" : ""})';
}
// Mapper (simulating generated code)
class CsvMapper``<T>`` {
final T Function(Map<String, String>) fromCsvRow;
CsvMapper(this.fromCsvRow);
List``<T>`` mapAll(List<Map<String, String>> rows) =>
rows.map(fromCsvRow).toList();
(List``<T>`` valid, List<(int, String)> errors) mapSafe(
List<Map<String, String>> rows) {
final valid = ``<T>``[];
final errors = <(int, String)>[];
for (var i = 0; i < rows.length; i++) {
try {
valid.add(fromCsvRow(rows[i]));
} catch (e) {
errors.add((i + 1, e.toString()));
}
}
return (valid, errors);
}
}
void main() {
// Simulated CSV data (already parsed into maps)
final csvRows = <Map<String, String>>[
{'order_id': 'ORD-001', 'total_amount': '1500.00', 'order_status': 'completed', 'category': 'Electronics'},
{'order_id': 'ORD-002', 'total_amount': '3200.50', 'order_status': 'completed', 'category': 'Electronics'},
{'order_id': 'ORD-003', 'total_amount': '890.00', 'order_status': 'pending', 'category': 'Clothing'},
{'order_id': 'ORD-004', 'total_amount': '50.00', 'order_status': 'completed'},
];
// Map to model objects
final mapper = CsvMapper``<Order>``(Order.fromCsvRow);
final (valid, errors) = mapper.mapSafe(csvRows);
print('=== DataPipeline CSV Import Report ===');
print('Rows: ${csvRows.length}');
print('Valid: ${valid.length}');
print('Errors: ${errors.length}');
if (errors.isNotEmpty) {
print('\nErrors:');
for (final (line, msg) in errors) {
print(' Line $line: $msg');
}
}
// Process valid orders
double totalRevenue = 0;
for (final order in valid) {
if (order.status == 'completed') {
totalRevenue += order.total;
}
print(' $order');
}
print('\nRevenue (completed): \$${totalRevenue.toStringAsFixed(2)} USD');
}
> **输出:** 在本地 DartPad 或 `dart run` 执行。Dart 课程所有示例基于 Dart 3.x / Flutter 3.x,运行结果会因 SDK 版本略有差异。
输出:
=== DataPipeline CSV Import Report ===
Rows: 4
Valid: 4
Errors: 0
Order(ORD-001, $1500.00, completed, Electronics)
Order(ORD-002, $3200.50, completed, Electronics)
Order(ORD-003, $890.00, pending, Clothing)
Order(ORD-004, $50.00, completed)
Revenue (completed): $5132.54 USD
❓ 常见问题
Q:为什么 Flutter 不支持 dart:mirrors? A:Flutter 使用 AOT 编译为原生代码,AOT 需要在编译时确定所有类型信息。反射在运行时动态查找类型,与 AOT 的 tree shaking 和编译优化冲突。
Q:注解本身有什么用? A:注解本身不执行任何逻辑,只是元数据。它们需要被反射读取(VM)或代码生成器处理(build_runner)才能发挥作用。
Q:build_runner 每次修改代码都要重新运行吗? A:是的,但有
--watch模式可以监听文件变化自动重新生成。开发时用 watch 模式,发布前用 build 模式。
Q:json_serializable 和手动写 fromJson 有什么区别? A:json_serializable 自动生成代码,避免手写错误,支持嵌套对象、自定义转换。手写简单但容易出错,嵌套对象更痛苦。
Q:Dart 将来会支持宏(Macros)吗? A:Dart 团队正在开发宏系统(macro package),旨在替代 build_runner 的部分功能,提供更好的开发体验。但目前尚未稳定。
Q:代码生成会增加包体积吗? A:会,因为生成的代码会被包含在编译产物中。但反射也会增加体积(阻止 tree shaking),两者差距不大。
Q:如何调试生成的代码? A:生成的 .g.dart 文件可以直接打开阅读和调试。build_runner 生成的代码是标准 Dart 代码,可以设断点。
📖 小节
- dart:mirrors 提供运行时反射能力,但仅限 VM 环境,Flutter AOT 和 Web 不可用
- 注解是元数据标记,需要反射或代码生成器来消费
- Dart 生态选择"代码生成"而非"反射",兼容 AOT/Flutter/Web
- reflectionless 设计哲学:编译时确定,零运行时开销,保持 tree shaking
- DataPipeline 用 @CsvField 注解标注字段映射,模拟代码生成
📝 作业
- 基础题(难度⭐):定义 3 个自定义注解(@ApiEndpoint、@Required、@DefaultValue),应用到一个类上。用 dart:mirrors 读取注解信息(注意只能在 VM 运行)。
- 进阶题(难度⭐⭐):创建一个 json_serializable 项目,为一个包含 5 个字段的 Order 类生成 fromJson/toJson 代码。查看生成的 .g.dart 文件,理解生成逻辑。
- 挑战题(难度⭐⭐⭐):设计一个简单的代码生成器:读取带有 @CsvField 注解的类定义,生成 fromCsvRow 静态方法。提示:可以用 source_gen 包或简单的字符串模板。