likes
comments
collection
share

搞一个最基本的纯Dart版本Retrofit

作者站长头像
站长
· 阅读数 38

对于熟悉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中, 任意一个普通的类, 都可以做注解. 所以, 下面的代码是完全合法的.

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注解的内容, 那就得这样:

  //用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了.

分解三: 动态代理

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的源码为:

class User 
    def method_missing(name, *args) 
        ....
    end

Dart中也是类似的机制, 只不过这个特殊方法不叫method_missing, 而是叫noSuchMethod方法. 我们来看个例子, 我们的Face类明明没有foo方法, 但我可以让调用foo方法不报错:

class Face {
  @override
  dynamic noSuchMethod(Invocation invocation) {
    print('called: $invocation');
    return 23;
  }
}

我们现在这样调用:

main() {
    dynamic obj = Face();
    final result = obj.foo();
    print("result = $result"); //=> result = 23
}

搞一个最基本的纯Dart版本Retrofit

这个其实就是我们的动态代理, 即我们可以让一些不存在的方法, 或是一些抽象方法, 在被调用时, 走到noSuchMethod里来, 然后我们在这里面调用Dio来做网络请求.

完结: 真正做一个Retrofit出来

先来看一下基本框架哦.

// 定义一个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来网络请求

  @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, ...等各种功能的填充了. 这里主要是入个门.

带@Query的Retrofit

现在我们再小小扩展一下, 加个Query参数, 把请求链接变为some_url?arg1=value1&arg2=value2式的链接.

同理, 先定义个新annotation类

```dart
// 定义一个annotation类
class Get {
  final String url;
  const Get(this.url);
}

class Query {
  final String argName;
  const Query(this.argName);
}

class UserService {
  @Get('https://www.somesite.com/api/Gut')
  String getUser(@Query("userId") int id);

  @override
  dynamic noSuchMethod(Invocation ivc) {
      ... //关键在这里
  }
}

那在noSuchMethod里, 基于上面的取到url后, 我们还要去取Query的元数据. 这时也是要用methodMirror自己的List<ParameterMirror> parameters成员. 这样其里面的每个参数的paramMirror.metadata就是参数带上的注解. 关键代码如下:

// 取到参数元信息
methodMirror.paramaters.forEach {paramMirror -> paraMirror.metadata}

// 取到参数值
List<dynamic> args = invokotion.positionalAreguments;

完整的代码则是:

  @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;

    // 取出参数名与参数值, 这样都放到GET的url里去
    final args = ivc.positionalArguments;
    if (args.length > 0) {
      url += "?";
      methodMirror.parameters.forEach2((param, index) {
        final paramName = param.metadata[0].reflectee.argName;
        url += "$paramName=${args[index]}&";
      });
      url = url.substring(0, url.length - 2); // 删除最后的"&"
    }
    
    Future resp = dio.get(url);  
    return resp; // 真正的Retrofit就走dio
  }
}

小结

这里整个应用了Class, Instance, Method, Parameter的Mirror反射相关知识, 得到了我们需要的注解信息, 即metadata.

其它Retrofit的注解, 如Post, Delete, Part, ... 这些都大同小异, 就不再赘述了. 这文章主要是介绍如何用元编程得到注解的信息, 以及做出一个实例(最基本的Retrofit框架)

后记

最后说一句, 这里说的Dart平台的Retrofit是有深意的. 真的它只能用于纯Dart平台, 是的, 没法用于Flutter.

原因就是Flutter为了怕开发乱搞, 禁止了dart:mirror的使用, 所以上面的办法都没法用于Flutter平台. 你要是想用, 就会报错:

搞一个最基本的纯Dart版本Retrofit

这怕也是为什么pub.dev上的retrofit库并没有使用反射, 而是使用了code generation的原因吧.

至于Flutter上如何仿制一个类似的Retrofit(不使用code generation的情况下), 我还在研究. 有结果了再和大家汇报.

转载自:https://juejin.cn/post/7290017749715599400
评论
请登录