跳转到正文
YGJoon 技术博客

Dart 反射和镜像详解

发布于:
·1.2k 字 · 约 3 分钟·作者:
用 mirrors 包在运行时窥探类型信息、动态调用实例与静态方法、读写属性,顺带讲清 Symbol 的用法与反射的性能代价。
Dart 反射和镜像详解文章封面
目录

简介

反射是在运行时检查、修改和调用程序元素的能力。Dart 通过 mirrors 包提供反射功能,可以用于运行时获取类型信息、调用方法等。

基本概念

镜像类型

import 'dart:mirrors';

// 实例镜像
InstanceMirror instanceMirror = reflect(object);

// 类镜像
ClassMirror classMirror = reflectClass(String);

// 库镜像
LibraryMirror libraryMirror = currentMirrorSystem().findLibrary(Symbol('dart.core'));

符号

// 创建符号
Symbol symbol = Symbol('methodName');

// 符号转字符串
String name = MirrorSystem.getName(symbol);

// 字符串转符号
Symbol symbol2 = Symbol('methodName');

类型反射

获取类型信息

class Person {
  String name;
  int age;
  
  Person(this.name, this.age);
  
  void sayHello() {
    print('Hello, I am $name');
  }

  void setAge(int newAge) {
    age = newAge;
  }

  int getAge() {
    return age;
  }
}

void inspectClass() {
  ClassMirror mirror = reflectClass(Person);
  
  // 获取构造函数
  mirror.declarations.forEach((Symbol name, DeclarationMirror declaration) {
    if (declaration is MethodMirror && declaration.isConstructor) {
      print('Constructor: ${MirrorSystem.getName(name)}');
    }
  });
  
  // 获取属性
  mirror.declarations.forEach((Symbol name, DeclarationMirror declaration) {
    if (declaration is VariableMirror) {
      print('Property: ${MirrorSystem.getName(name)}');
    }
  });
  
  // 获取方法
  mirror.declarations.forEach((Symbol name, DeclarationMirror declaration) {
    if (declaration is MethodMirror && !declaration.isConstructor) {
      print('Method: ${MirrorSystem.getName(name)}');
    }
  });
}

方法调用

调用实例方法

void invokeMethod() {
  var person = Person('John', 30);
  InstanceMirror mirror = reflect(person);
  
  // 调用方法
  mirror.invoke(Symbol('sayHello'), []);
  
  // 调用带参数的方法
  mirror.invoke(Symbol('setAge'), [25]);
  
  // 获取返回值
  var result = mirror.invoke(Symbol('getAge'), []);
  print('Age: ${result.reflectee}');
}

调用静态方法

class MathUtils {
  static int max(int a, int b) => a > b ? a : b;
}

void invokeStaticMethod() {
  ClassMirror mirror = reflectClass(MathUtils);
  
  // 调用静态方法
  var result = mirror.invoke(Symbol('max'), [10, 20]);
  print('Max: ${result.reflectee}');
}

属性访问

读写属性

void accessProperties() {
  var person = Person('John', 30);
  InstanceMirror mirror = reflect(person);
  
  // 读取属性
  var name = mirror.getField(Symbol('name')).reflectee;
  print('Name: $name');
  
  // 设置属性
  mirror.setField(Symbol('name'), 'Jane');
}

属性监听

void watchProperties() {
  var person = Person('John', 30);
  InstanceMirror mirror = reflect(person);
  
  // 监听属性变化
  mirror.type.declarations.forEach((Symbol name, DeclarationMirror declaration) {
    if (declaration is VariableMirror) {
      var oldValue = mirror.getField(name).reflectee;
      // 实现属性变化通知
    }
  });
}

完整示例

import 'dart:mirrors';

class User {
  String name;
  int age;
  bool _isAdmin;
  
  User(this.name, this.age, [this._isAdmin = false]);
  
  void greet() {
    print('Hello, I am $name');
  }
  
  bool checkAccess(String resource) {
    return _isAdmin;
  }
  
  @override
  String toString() => 'User($name, $age)';
}

class ReflectionHelper {
  static void inspectObject(Object object) {
    InstanceMirror instanceMirror = reflect(object);
    ClassMirror classMirror = instanceMirror.type;
    
    print('Class: ${MirrorSystem.getName(classMirror.simpleName)}');
    
    // 检查属性
    print('\nProperties:');
    classMirror.declarations.forEach((Symbol name, DeclarationMirror declaration) {
      if (declaration is VariableMirror) {
        var propertyName = MirrorSystem.getName(name);
        var value = instanceMirror.getField(name).reflectee;
        print('  $propertyName = $value');
      }
    });
    
    // 检查方法
    print('\nMethods:');
    classMirror.declarations.forEach((Symbol name, DeclarationMirror declaration) {
      if (declaration is MethodMirror && !declaration.isConstructor) {
        var methodName = MirrorSystem.getName(name);
        var parameters = declaration.parameters
            .map((p) => MirrorSystem.getName(p.simpleName))
            .join(', ');
        print('  $methodName($parameters)');
      }
    });
  }
  
  static void invokeMethod(Object object, String methodName, List arguments) {
    InstanceMirror instanceMirror = reflect(object);
    instanceMirror.invoke(Symbol(methodName), arguments);
  }
  
  static void setProperty(Object object, String propertyName, dynamic value) {
    InstanceMirror instanceMirror = reflect(object);
    instanceMirror.setField(Symbol(propertyName), value);
  }
  
  static dynamic getProperty(Object object, String propertyName) {
    InstanceMirror instanceMirror = reflect(object);
    return instanceMirror.getField(Symbol(propertyName)).reflectee;
  }
}

void main() {
  var user = User('John', 30, true);
  
  // 检查对象
  ReflectionHelper.inspectObject(user);
  
  // 调用方法
  ReflectionHelper.invokeMethod(user, 'greet', []);
  
  // 修改属性
  ReflectionHelper.setProperty(user, 'name', 'Jane');
  
  // 读取属性
  var age = ReflectionHelper.getProperty(user, 'age');
  print('Age: $age');
}

最佳实践

  1. 谨慎使用反射
  2. 注意性能影响
  3. 处理好异常情况
  4. 提供替代方案
  5. 合理使用缓存

注意事项

  1. 反射在生产环境可能受限
  2. 影响代码大小和性能
  3. 可能破坏类型安全
  4. 不是所有平台都支持
  5. 注意版本兼容性

实战警告:dart:mirrors 在 Flutter 里根本跑不起来

本文通篇讲 dart:mirrors,但漏了最关键的一句:它在 Flutter 里不可用。只能在 Dart VM 跑,Flutter 的 AOT 编译 + tree-shaking 和运行时反射是互斥的——反射要求”所有代码都留着,万一运行时要调”,tree-shaking 要求”没用到的全删掉”,这两个目标打架,所以 Flutter 直接禁了 mirrors(Web 端同样不行)。

那 Flutter 里”运行时看类型信息”的需求怎么办?我给你三个真实替代方案:

  1. 编译期代码生成(json_serializable、freezed):把”运行时反射”翻译成”编译时生成”。这是官方推荐路线。
  2. 手写映射表:我的 ygking_json_to_dart_model 就是例子——_getFieldType 里用 is 判断手动分发类型。low,但零依赖、零构建步骤,调试工具场景下比配 build_runner 快得多。
  3. reflectable 包:用编译期生成”反射元数据”,曲线救国。适合真的需要反射语义(插件系统那种)又跑在 AOT 上的场景。

一句话:在 Flutter 里看到 dart:mirrors 的教程,先问”这代码能跑在手机上吗”。本文的例子在 Dart VM 命令行里能跑,打包成 App 就编译失败——这个前提不讲清楚,读者会浪费一下午。

参考:flutterfever《Metaprogramming in Dart》

总结

反射和镜像提供了强大的运行时编程能力,但应该谨慎使用。在开发工具、测试框架等场景下比较适合使用反射,而在生产应用中应该尽量避免使用。


分享这篇文章:


相关文章

上一篇
Dart 隔离区(Isolate)详解
下一篇
Dart 继承和接口详解