retrofit的api是如何实现的

543 阅读4分钟

  retrofit这样的网络请求库在我们开发项目中是首选的框架,在我们日常中通过retrofit调用网络请求的一件非常轻松愉悦的事情,那我们应该怎么封装出类似api调用的框架呢?

  首先我们先来看一看retrofit的代码

1.首先我们需要定义接口
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

2.创建Retrofit对象
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();
    
3.创建service对象
GitHubService service = retrofit.create(GitHubService.class);

4.调用service方法请求接口
Call<List<Repo>> repos = service.listRepos("octocat");

  我们首先分析下定义的接口中包含了@GET、@PATH注解用于合成求情的url及参数, 这个GitHubService接口的指责我们可以得出通过注解的方式定义请求体及请求方法,第二部总的来说就是通过Retrofit对象创建出一个GitHubService的实现类,我们最终通过GitHubService的实现类调用我们定义的方法进行请求,接下来我们一步一步拆解来实现这些功能,当然我并不会实现一个真正可请求的功能。
首先我们先实现接口这个文件的实现,我们需要实现两个注解:GET、Path,GET注解需要接收一个参数代表请求的路径,Path接收一个参数用于替换请求路径中对应的参数。

/*
*标注get请求
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class GET(val value: String)

/*
*标注post请求
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class POST(val value: String)

/*
*标注get请求参数
*/
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Query(val value: String)

/*
*标注post请求
*/
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Field(val value: String)

/*
*标注header请求
*/
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class Header(val value: String)

两个注解都非常的简单,不做过多的解释。下面呢我们需要将接口给定义出来。

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Query("user") String user);
}

现在我们的基础代码已经写完了,那接下来继续考虑的就是怎么去实现接口的调用逻辑呢?方法还是有几种选择的,这边选择动态代理的实现,相对于其他的实现方案来说挺简单的。既然选择动态代理我们需要涉及到的类有InvocationHandler和Proxy,这两个类(前者是接口)的使用也非常简单的,首先我们需要实现InvocationHandler的一个类,然后使用Proxy.newProxyInstance将我们的代理类和接口进行绑定并得到一个实现类,那么我们通过这个实现类调用具体的请求方法会被InvocationHandler劫持到invoke方法中,在invoke方法中我们需要对方法的注解进行解析并拼装成底层网络库需要的格式,最后就可以调用网络请求来实现目的了。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

InvocationHandler仅需要实现一个方法就可以了,对应的三个参数依次为proxy(被代理的对象)、method(proxy的方法)、args(参数列表)。

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

Proxy.newProxyInstance的参数依次为loader(被代理对象的ClassLoader)、interfaces(被代理对象所实现的接口)、h(代理类实例)。

首先我们先创建一个GitHubServiceInvocationHandler

class GitHubServiceInvocationHandler : InvocationHandler {
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any {
        method?.run { method
            var isGet = true
            //请求的url
            var url = ""
            for (ann in method.annotations) {
                if (ann is GET) {
                    url = ann.value
                } else if (ann is POST) {
                    url = ann.value
                    isGet = false
                }
            }

            //解析请求参数
            val parameters = method.parameters
            var body = mutableMapOf<String, Any>()
            //请求头
            var header = mutableMapOf<String, Any>()

            for (param in parameters) {
                val index = parameters.indexOf(param)
                val arg = args?.get(index) ?: ""

                for (ann in param.annotations) {
                    if (ann is Query) {
                        val queryName = ann.value

                        url = url.replace("{$queryName}", "$arg")
                    } else if (ann is Field) {
                        val queryName = ann.value

                        body[queryName] = arg
                    } else if (ann is Header) {
                        val queryName = ann.value

                        header[queryName] = arg
                    }
                }
            }

            println("处理完后的url:$url")
            println("处理完后的body:$body")
            println("处理完后的header:$header")

            //调用底层网络框架
            println("触发HTTP请求...")
        }
        return People("river")
    }
}

可以看出代码的逻辑也非常的简单,就是通过反射拿到不同的注解进行数据上的拼装。

val newProxyInstance = Proxy.newProxyInstance(
        GitHubService::class.java.classLoader,
        listOf<Class<*>>(GitHubService::class.java).toTypedArray(), gitHubServiceInvocationHandler
    ) as GitHubService
newProxyInstance.listRepos("river")

到此未知我们实现了一个壳,核心的逻辑呢就这些,当然实现的非常简单。总的代码量也不是很多,涉及到的知识点也仅仅只有动态代理和反射。