对于熟悉Retrofit的Android程序员来说, Retrofit确实是可读性极佳, 写起来也没有过多多余的重复代码要写, 对开发人员极是友好. 那我们有办法在dart上也搞一个吗?
于是我找了下pub.dev, 果然找到一个retrofit库. 但我个人对这个库不太满意, 因为它要用code generation来生成一些dart代码. 我一般不太喜欢用flutter的code generation, 而更倾向于自己用groovy或ruby写一个自己的脚本来完成这些工作. 另外, Android版本的Retrofit也没有这项生成代码的中间工作, 于是我就想更加简单地做到一个retrofit框架. 下面就是我的尝试与成功的过程
分解一: 注解 (Annotation)
Java中的Annotation用@interface
来定义, Kotlin中用annotation class
来定义. 但在Dart中, 任意一个普通的类, 都可以做注解. 所以, 下面的代码是完全合法的.
dart
class Todo {
final int id;
final String name;
const FishTodo(this.id, this.name);
}
class Worker {
@Todo(23, 'air fly')
void work(bool isRepeat) {
print('work : $isRepeat');
}
}
分解二: 使用反射来读取Annotation中的值
dart中的反射多是在dart:mirror
这个包里, 它里面有各种类, 如InstanceMirror, MethodMirror, ParameterMirror, .... , 分别对应了类, 方法, 参数等各种反射相关的类.
(你可以理解为java中的Class, Method, ... 这些反射的类.)
而这些mirror类有一个metadata
成员, 这个成员就是我们的注解了. 所以我们若想得到上面的Todo注解的内容, 那就得这样:
dart
//用reflect(obj)得到这个obj的反射对象. 这个
InstanceMirror mirror = reflect(Worker());
//其实就是得到了这个对象的所有方法.
// 其中的key, 即Symbol, 可以暂时理解为方法名;
// 其中的value, 即MethodMirror, 则是每一个方法对应的反射
Map<Symbol, MethodMirror> methods = mirror.type.instanceMembers; //mirror.type是一个ClassMirror对象
// 查看所有方法, 看哪个有Todo注解
methods.forEach( (symbol, methodMirror) {
try {
List<InstanceMirror> annotationList = methodMirror.metadata;
InstanceMirror annotation = annotationList.firstWhere ( (meta) => meta.reflectee is Todo)
// 这个mirror的reflectee就是代表此反射的真正对象, 在这即为Todo注解的那个对象了
Todo todo = annotation.reflectee;
print('name = ${todo.anme}, id = ${todo.id}'); //=> name = air fly, id = 23
} catch (err) {
// 上面的firstWhere没找到就会扔异常, 这里就catch住, 不做任何处理即可. 毕竟不是每个方法都有Todo注解的
}
}
上面代码中我用注释详细讲解了一些细节. 另外想说明的就是:
1). dart中的mirror = reflect(obj).type
, 类似于我们的Class mirror = obj::class.java
一样.
2). 而Map<Symbol, MethodMirror> methods = mirror.type.instanceMembers;
, 则是类似 java中的Method[] methods = clazz.getMethods()
, 用来得到各种方法
3). mirror.metadata
则是表示它的元数据, 或者说表示它的Annotation了.
分解三: 动态代理
在java版本的Retrofit中, 我们定义了一个网络接口的interface, 只定义了方法. 那实际使用上我们却能得到这个interface的对象, 这其实是通过动态代理做到的. 具体的java版本做法, 可以看 从零开始实现一个 mini-Retrofit 框架这篇文章, 解释得很好. 主体代码就是:
java
Proxy.newProxyInstance(classLoader, new Class[]{service},
new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final Annotation[] annotations = method.getAnnotations();
Get annotation = annotations.first { it is GET }
final Request request = new Request.Builder()
.url(annotation.value())
.get().build();
return okHttpClient.newCall(request); }
});
好了, 问题回到Dart, Dart中有这样的动态代理吗? 即表面上你是在调用某一个接口, 但其实是在调用InnvocationHandler这个代理类?
: 不好意思, 没有. Dart没有这样的动态代理类.
但是, 但是, Dart有动态代理的功能, 这个功能类似Ruby的动态代理功能. 熟悉Ruby的同学就知道, Ruby元编程里一个重要方法就是method_missing
, 即你的类明明没有这个方法, 但你要是调用这个不存在的方法, 其实就是导致这个类的method_missing
方法被调用.
Ruby的源码为:
ruby
class User
def method_missing(name, *args)
....
end
Dart中也是类似的机制, 只不过这个特殊方法不叫method_missing
, 而是叫noSuchMethod
方法. 我们来看个例子, 我们的Face类明明没有foo方法, 但我可以让调用foo方法不报错:
dart
class Face {
@override
dynamic noSuchMethod(Invocation invocation) {
print('called: $invocation');
return 23;
}
}
我们现在这样调用:
dart
main() {
dynamic obj = Face();
final result = obj.foo();
print("result = $result"); //=> result = 23
}

这个其实就是我们的动态代理, 即我们可以让一些不存在的方法, 或是一些抽象方法, 在被调用时, 走到noSuchMethod
里来, 然后我们在这里面调用Dio来做网络请求.
完结: 真正做一个Retrofit出来
先来看一下基本框架哦.
dart
// 定义一个annotation类
class Get {
final String url;
const Get(this.url);
}
class UserService {
@Get('https://www.somesite.com/api/Gut')
String getUser();
@override
dynamic noSuchMethod(Invocation ivc) {
... //关键在这里
}
}
// 这样我们就可以使用它了:
void main() {
UserService http = UserService();
final resp = http.getUser();
print('resp = $resp');
}
填充那个关键的动态代理方法
关键都在这个noSuchMethod
方法里, 我们要用它得到方法mirror, 以及方法所带的annotation, 并调用Dio来网络请求
dart
@override
dynamic noSuchMethod(Invocation ivc) {
final objMirror = reflect(this);
// ivc.memberName即我们所调用的方法名
final methodMirror = objMirror.type.instanceMembers[ivc.memberName]!;
// 找到Get这个annotation注解对象
final annoMirror = methodMirror.metadata.firstWhere( (meta) meta is Get );
String url = annoMirror.reflectee.url;
Future resp = dio.get(url);
return resp; // 返回dio得到的response
}
这样一来, dart平台的Retrofit就做完了 (基本版本). 要想完善, 自然带得有Put, Delete, Post, Query, PathArgument, ...等各种功能的填充了. 这里主要是入个门.
后记
最后说一句, 这里说的Dart平台的Retrofit
是有深意的. 真的它只能用于纯Dart平台, 是的, 没法用于Flutter.
原因就是Flutter为了怕开发乱搞, 禁止了dart:mirror
的使用, 所以上面的办法都没法用于Flutter平台. 你要是想用, 就会报错:

这也怕也是为什么pub.dev上的retrofit库并没有使用反射, 而是使用了code generation的原因吧.
至于Flutter上如何仿制一个类似的Retrofit(不使用code generation的情况下), 我还在研究. 有结果了再和大家汇报.