手写简易版 Retrofit

本文详细讲解了Retrofit的使用,涉及接口定义、动态代理创建、注解解析和参数传递,帮助理解Retrofit如何通过OkHttp进行网络请求
摘要由CSDN通过智能技术生成

一、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() {
        // 1.生成 SimpleRetrofit 的实例
        SimpleRetrofit retrofit = new SimpleRetrofit.Builder().baseUrl("https://restapi.amap.com").build();
        // 2.使用 SimpleRetrofit 为 Api 接口生成一个动态代理
        Api api = retrofit.create(Api.class);
        // 3.调用 Api 接口中的方法,拿到一个 OkHttp 中的任务对象 call
        Call call = api.getWeather("北京", "13cb58f5884f9749287abbead9c658f2");

        // 4.异步方式执行 call,即将其入队
        call.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 时序图

2.1 注解定义

我们要实现一个极简版的 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();
}

2.2 SimpleRetrofit 的初始化

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 对象。

2.3 创建动态代理对象

SimpleRetrofit 对象生成后,在 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) {
                // 在 build() 中解析方法和参数
                serviceMethod = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, serviceMethod);
            }
        }
        return serviceMethod;
    }

2.4 解析方法和参数

在 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;
        }
    }
       
}

解析方法注解主要是为了获取使用哪种 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);
    }

2.5 将解析结果拼接成网络请求并发送

最后在 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);
    }

使用者拿到 Call 之后就可以发送该请求并获取结果了。

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值