跳转到正文
YGJoon 技术博客

Dart 元数据和注解详解

发布于:
·1.1k 字 · 约 3 分钟·作者:
先吃透 @deprecated、@override 等内置注解,再自定义复杂注解,最后用反射在运行时读取注解信息,元数据三步走。
Dart 元数据和注解详解文章封面
目录

简介

元数据(Metadata)是用于为代码添加额外信息的方式。在 Dart 中,元数据以 @ 符号开头,可以应用于类、方法、变量、参数和其他程序元素。

内置注解

@deprecated

// 标记已废弃的 API
@deprecated
void oldMethod() {
  print('This method is deprecated');
}

// 添加废弃说明
@Deprecated('Use newMethod() instead')
void oldMethod2() {
  print('This method is deprecated');
}

@override

class Animal {
  void makeSound() {
    print('Some sound');
  }
}

class Dog extends Animal {
  @override
  void makeSound() {
    print('Woof!');
  }
}

required

class Person {
  final String name;
  final int age;

  // 空安全时代用 required 关键字;旧写法 @required 来自 package:meta,现已不推荐
  Person({
    required this.name,
    required this.age,
  });
}

自定义注解

定义注解

class Todo {
  final String message;
  final String assignee;
  
  const Todo(this.message, {this.assignee = ''});
}

// 使用注解
@Todo('Implement this method', assignee: 'john')
void implementMe() {
  throw UnimplementedError();
}

复杂注解

class JsonKey {
  final String? name;
  final bool ignore;
  final Object? defaultValue;
  
  const JsonKey({
    this.name,
    this.ignore = false,
    this.defaultValue,
  });
}

class User {
  @JsonKey(name: 'user_name')
  final String name;
  
  @JsonKey(ignore: true)
  final String password;
  
  @JsonKey(defaultValue: 0)
  final int age;
  
  User(this.name, this.password, this.age);
}

反射使用

获取注解

import 'dart:mirrors';

void printAnnotations(Object object) {
  InstanceMirror instanceMirror = reflect(object);
  ClassMirror classMirror = instanceMirror.type;
  
  // 获取类的注解
  classMirror.metadata.forEach((metadata) {
    print('Class annotation: ${metadata.reflectee}');
  });
  
  // 获取方法的注解
  classMirror.declarations.forEach((symbol, declarationMirror) {
    if (declarationMirror is MethodMirror) {
      declarationMirror.metadata.forEach((metadata) {
        print('Method annotation: ${metadata.reflectee}');
      });
    }
  });
}

完整示例

import 'dart:mirrors';

// 自定义注解
class Route {
  final String path;
  final String name;
  final bool auth;
  
  const Route({
    required this.path,
    required this.name,
    this.auth = false,
  });
}

class Api {
  final String path;
  final String method;
  final bool cache;

  const Api({
    required this.path,
    this.method = 'GET',
    this.cache = false,
  });
}

// 演示用的数据类
class User {
  final String name;
  User(this.name);
}

// 使用注解
@Route(
  path: '/users',
  name: 'users_page',
  auth: true,
)
class UsersPage {
  @Api(
    path: '/api/users',
    method: 'GET',
    cache: true,
  )
  Future<List<User>> getUsers() async {
    // 实现获取用户列表
    return [];
  }
  @Api(
    path: '/api/users',
    method: 'POST',
  )
  Future<void> createUser(User user) async {
    // 实现创建用户
  }
  
  @deprecated
  void oldMethod() {
    print('This method is deprecated');
  }
}

// 处理注解
void processAnnotations() {
  final usersPage = UsersPage();
  
  // 获取类的注解
  final route = reflect(usersPage)
      .type
      .metadata
      .firstWhere((m) => m.reflectee is Route)
      .reflectee as Route;
      
  print('Route: ${route.path}, Auth: ${route.auth}');
  
  // 获取方法的注解
  final mirror = reflect(usersPage);
  mirror.type.declarations.forEach((symbol, declarationMirror) {
    if (declarationMirror is MethodMirror) {
      final apis = declarationMirror.metadata
          .where((m) => m.reflectee is Api)
          .map((m) => m.reflectee as Api);
          
      for (var api in apis) {
        print('API: ${api.method} ${api.path}, Cache: ${api.cache}');
      }
    }
  });
}

最佳实践

  1. 合理使用内置注解
  2. 为自定义注解提供清晰的文档
  3. 注意注解的性能影响
  4. 避免过度使用注解
  5. 注意反射的使用限制

注意事项

  1. 反射在生产环境可能受限
  2. 注解应该是常量
  3. 注意注解的作用域
  4. 合理处理注解的默认值
  5. 注意版本兼容性

实战案例:我为什么没用注解

Dart 注解最出名的用法是 json_serializable:

@JsonSerializable()
class User {
  final String name;
  User({required this.name});
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

@JsonSerializable() 就是元数据——它本身不干活,是告诉 build_runner:“这个类,生成序列化代码”。注解在 Dart 里永远只是”便签纸”,真正干活的是读便签的工具(编译器插件、代码生成器)。

而我的 ygking_json_to_dart_model 偏偏一个注解都没用。原因很简单:注解的价值在于”声明式配置”,适合”我写类、工具生成代码”的流程;我的工具是反过来的——“用户给 JSON、我现场生成类”,注解根本插不进来。_getFieldType 里判断 value is List 就生成 List<...>,这种运行时才能决定的事,编译期注解帮不上忙。

所以注解的适用边界很清楚:当”配置”和”代码”要写在一起、且有工具消费这些配置时,用注解(json_serializable、freezed、retrofit 全是这个模式)。如果只是想给代码加注释,用 /// 文档注释;如果想在运行时读标记——不好意思,Flutter 里 dart:mirrors 被禁了,这条路不通(见反射那篇)。

参考:Dart 官方文档(dart.dev)“Metadata” 章节

总结

元数据和注解是 Dart 中用于添加额外信息的重要机制。通过合理使用注解,可以实现更好的代码文档、编译时检查和运行时行为定制。理解并掌握注解的使用对于开发高质量的 Dart 应用很有帮助。


分享这篇文章:


相关文章

上一篇
Dart Stream 和 Future 详解
下一篇
Dart 变量和数据类型详解