手写简易版 Retrofit

一、Retrofit 基本使用

这里只实现最基本的使用,适配器和转换器等并未使用。

先定义 Api 接口:

public interface Api {

    @GET("/v3/weather/weatherInfo")
    Call getWeather(@Query("city") String city, @Query("key") String key);

    @POST("/v3/weather/weatherInfo")
    Call postWeather(@Field("city") String city, @Field("key") String key);
}

分别使用 POST 和 GET 请求去获取天气数据,这里使用的是高德地图的 API。

接下来创建 Retrofit 对象,获取 Api 实例,用异步方式执行 Api 中的请求:

    private void request() {
        SimpleRetrofit retrofit = new SimpleRetrofit.Builder().baseUrl("https://restapi.amap.com").build();
        Api api = retrofit.create(Api.class);
        Call getCall = api.getWeather("北京", "13cb58f5884f9749287abbead9c658f2");

        getCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                System.out.println(response.body().string());
            }
        });
    }

二、手写代码

写代码之前要了解一个前提,就是 Retrofit 其实是通过封装 OKHttp 才拥有了网络访问能力的,实际执行网络请求的是 OKHttp。Retrofit 要做的是为网络请求接口生成动态代理对象,并在请求方法被调用时,在动态代理的 InvocationHandler 中解析注解,把要使用的网络请求方法和参数解析出来生成 OKHttp 的 Request 对象,最后由 OKHttp 发送请求。

SimpleRetrofit 时序图

我们要实现的是一个极简版的 Retrofit(只是为了辅助更好的理解 Retrofit 框架),请求方法只实现了 GET、POST,参数注解只支持 @Query、@Field:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GET {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface POST {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Query {
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Field {
    String value();
}

Retrofit 使用 Builder 创建其对象:

public class SimpleRetrofit {

    HttpUrl baseUrl;
    Call.Factory callFactory;

    public SimpleRetrofit(Builder builder) {
        this.baseUrl = builder.baseUrl;
        this.callFactory = builder.callFactory;
    }
    
    static class Builder {

        HttpUrl baseUrl;
        Call.Factory callFactory;

        Builder baseUrl(String baseUrl) {
            this.baseUrl = HttpUrl.parse(baseUrl);
            return this;
        }

        public SimpleRetrofit build() {
            // 先做参数校验
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }

            if (callFactory == null) {
                callFactory = new OkHttpClient();
            }

            return new SimpleRetrofit(this);
        }
    }
}

Call.Factory 是生成 Call 对象的工厂,其唯一实现类为 OKHttpClient,调用其 newCall(Request) 方法可以生成 Call 对象,最后要通过这个方法把我们解析出来的数据封装在 Request 中以生成 Call 对象。

MyRetrofit 对象生成后,通过调用 create() 生成接口对象。很明显是在 create() 中为接口生成了动态代理,同时做了接口方法的解析和调用:

    
    private final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
    
    public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object object, Method method, Object[] args) throws Throwable {
                        ServiceMethod serviceMethod = loadServiceMethod(method);
                        return serviceMethod.invoke(args);
                    }
                });
    }

    /**
     * DLC 方式获取 ServiceMethod,如果没有解析过就解析该方法
     * @param method 动态代理执行的接口方法
     * @return 解析后的方法对象
     */
    private ServiceMethod loadServiceMethod(Method method) {
        // 先不加锁,避免性能损失
        ServiceMethod serviceMethod = serviceMethodCache.get(method);
        if (serviceMethod != null) return serviceMethod;

        // 避免多线程下重复解析
        synchronized (serviceMethodCache) {
            serviceMethod = serviceMethodCache.get(method);
            if (serviceMethod == null) {
                serviceMethod = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, serviceMethod);
            }
        }
        return serviceMethod;
    }

在 ServiceMethod 的 Builder 中解析方法和参数注解:

public class ServiceMethod {

    private final String httpMethod;
    private final Call.Factory callFactory;
    private final HttpUrl baseUrl;
    private final String relativeUrl;
    private final ParameterHandler[] parameterHandlers;

    private FormBody.Builder formBuilder;
    private FormBody formBody;
    private HttpUrl.Builder urlBuilder;

    public ServiceMethod(Builder builder) {
        baseUrl = builder.retrofit.baseUrl;
        callFactory = builder.retrofit.callFactory;
        httpMethod = builder.httpMethod;
        relativeUrl = builder.relativeUrl;
        parameterHandlers = builder.parameterHandlers;
        boolean hasBody = builder.hasBody;

        // 如果有请求体,创建一个 OKHttp 请求体对象
        if (hasBody) {
            formBuilder = new FormBody.Builder();
        }
    }
    
    static class Builder {

        private final SimpleRetrofit retrofit;
        private final Method method;
        private final Annotation[] annotations;
        // 方法上有 n 个参数,每个参数又有 m 个注解,用一个 nxm 的数组保存
        private final Annotation[][] parameterAnnotations;

        String httpMethod;
        boolean hasBody;
        String relativeUrl;
        ParameterHandler[] parameterHandlers;

        public Builder(SimpleRetrofit retrofit, Method method) {
            this.retrofit = retrofit;
            this.method = method;
            annotations = method.getAnnotations();
            parameterAnnotations = method.getParameterAnnotations();
        }
        
        public ServiceMethod build() {
            // 解析方法上的注解
            for (Annotation annotation : annotations) {
                if (annotation instanceof GET) {
                    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
                } else if (annotation instanceof POST) {
                    parseHttpMethodAndPath("POST", ((POST) annotation).value(), true);
                }
            }

            // 解析方法参数上的所有注解,把注解值存入 ParameterHandler[] 中
            int length = parameterAnnotations.length; // 方法上的参数个数
            parameterHandlers = new ParameterHandler[length];
            for (int i = 0; i < length; i++) {
                // 一个参数上的所有注解
                Annotation[] annotations = parameterAnnotations[i];
                parameterHandlers[i] = parseParameter(annotations);
            }

            return new ServiceMethod(this);
        }

        private ParameterHandler parseParameter(Annotation[] annotations) {

            // 根据注解类型创建对应的 ParameterHandler
            ParameterHandler result = null;

            for (Annotation annotation : annotations) {
                ParameterHandler annotationAction = parseParameterAction(annotation, annotations);

                // 如果当前检查的注解并不是我们能处理的,就继续遍历下一个
                if (annotationAction == null) {
                    continue;
                }

                // 如果 result 不为 null 说明之前遍历时已经找到了 SimpleRetrofit 能处理的注解
                // 不允许一个参数上被多个 SimpleRetrofit 的参数注解标注,抛异常
                if (result != null) {
                    throw new IllegalArgumentException("Multiple Retrofit annotations found, only one allowed.");
                }

                result = annotationAction;
            }

            // 遍历完都没找到说明这个参数没有被 SimpleRetrofit 注解标注,不应该被检查
            if (result == null) {
                throw new IllegalArgumentException("No Retrofit annotation found.");
            }

            return result;
        }

        private ParameterHandler parseParameterAction(Annotation annotation, Annotation[] annotations) {
            if (annotation instanceof Query) {
                String key = ((Query) annotation).value();
                return new ParameterHandler.QueryParameterHandler(key);
            } else if (annotation instanceof Field) {
                String key = ((Field) annotation).value();
                return new ParameterHandler.FieldParameterHandler(key);
            }

            return null;
        }

        private void parseHttpMethodAndPath(String httpMethod, String value, boolean hasBody) {
            // 规定一个方法上只能有一个 httpMethod 注解,否则抛出异常
            if (this.httpMethod != null) {
                String message = String.format("Only one HTTP method is allowed. Found: %s and %s.",
                        this.httpMethod, httpMethod);
                throw new IllegalArgumentException(message);
            }

            this.httpMethod = httpMethod;
            this.hasBody = hasBody;

            if (value == null) {
                return;
            }

            this.relativeUrl = value;
        }
    }
       
}

build() 中负责解析方法注解和方法参数注解。解析方法注解主要是为了获取使用哪种 HTTP 方法(GET、POST)、是否有请求体以及相对地址;解析方法参数注解是为了把被 @Field 或 @Query 注解的参数的值,即网络请求的 key 保存在 ParameterHandler[] 中。比如说对于 getWeather():

    @GET("/v3/weather/weatherInfo")
    Call getWeather(@Query("city") String city, @Query("key") String key);

@Query 注解的值分别为 city、key,那么就把 city 和 key 分别传入 ParameterHandler[] 中保存,而这两个 key 对应的 value 会在调用 ServiceMethod 的 invoke() 方法时传入。

ParameterHandler 的作用是保存网络请求的 key,并把 key-value 回调给 ServiceMethod:

public abstract class ParameterHandler {

    abstract void apply(ServiceMethod serviceMethod, String value);

    static class QueryParameterHandler extends ParameterHandler {

        String key;

        public QueryParameterHandler(String key) {
            this.key = key;
        }

        @Override
        void apply(ServiceMethod serviceMethod, String value) {
            serviceMethod.addQueryParameter(key, value);
        }
    }

    static class FieldParameterHandler extends ParameterHandler {

        String key;

        public FieldParameterHandler(String key) {
            this.key = key;
        }

        @Override
        void apply(ServiceMethod serviceMethod, String value) {
            serviceMethod.addFieldParameter(key, value);
        }
    }
}

回调方法一个是处理 GET 请求的,一个是处理 POST 请求的:

    // get 请求,把 key-value 拼接到 url 中
    public void addQueryParameter(String key, String value) {
        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }

        urlBuilder.addQueryParameter(key, value);
    }

    // post 请求,把 key-value 放到请求体中
    public void addFieldParameter(String key, String value) {
        formBuilder.add(key, value);
    }

最后在 invoke() 中生成 OKHttp 的 Request 对象并调用 CallFactory 的 newCall(Request) 生成 Call:

    public Object invoke(Object[] args) {
        for (int i = 0; i < parameterHandlers.length; i++) {
            ParameterHandler parameterHandler = parameterHandlers[i];
            parameterHandler.apply(this, args[i].toString());
        }

        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }
        HttpUrl httpUrl = urlBuilder.build();

        if (formBuilder != null) {
            formBody = formBuilder.build();
        }

        Request request = new Request.Builder().url(httpUrl).method(httpMethod, formBody).build();
        return callFactory.newCall(request);
    }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容