手撸简易版的Retrofit

Posted 清风Coolbreeze

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手撸简易版的Retrofit相关的知识,希望对你有一定的参考价值。

今天废话不多说,来一个学以致用篇,手撸一个简易版的Retrofit!用到的知识点,反射、注解加动态代理还有构建者模式!尤其是动态代理,如果理解了其原理,这篇文章看起来也会容易一些,这些知识点,以前的文章都写过,但是都太偏理论,所以今天来一个更综合的实战!

对反射和动态代理不熟悉的同学,请阅读 搞懂Java反射和JDK里的动态代理

对注解不熟悉的同学,请阅读 搞懂Java高级特性---注解

1、Retrofit的使用

Retrofit并不是一个网络请求框架,它只是一个对OKHttp封装的一个框架,用Retrofit只是让我们更容易的使用Okhttp,所以我们要手撸它,肯定得先了解它,最起码知道怎么使用

1.1、声明Service接口

public interface WeatherApi {

    @POST("/v3/weather/weatherInfo")
    @FormUrlEncoded
    Call<ResponseBody> postWeather(@Field("city") String city, @Field("key") String key);


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

1.2、通过Retrofit来获取Service接口的代理对象

  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Retrofit retrofit = new Retrofit.Builder().baseUrl("https://restapi.amap.com")
               .build();

        weatherApi = retrofit.create(WeatherApi.class);

     
    }

1.3、通过代理对象执行相应的方法,来获取一个Call,然后通过call执行Http请求

   public void get(View view) {
        Call<ResponseBody> call = weatherApi.getWeather("110101", "ae6c53e2186f33bbf240a12d80672d1b");
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()){
                    ResponseBody body = response.body();
                    try {
                        String string = body.string();
                        Log.i(TAG, "onResponse get: " + string);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        body.close();
                    }
                }

            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {

            }
        });
    }

上面就是我们使用Retrofitfit来实现一个网络请求,然后我们分析一下Retrofit在这当中的作用是什么,他就是通过动态代理注解加反射,来把请求的url参数和方法等信息,集成到一个call里并把Call返回回来,下面就让我们手撸一个简易版的

2、Retrofit简易版的实现

2.1、Retrofit构建者的实现

实现自己的Retrofit,Retrofit真是的框架里,他用的是构建者模式,构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。

public class MyRetrofit {

    final Map<Method, ServiceMethod> serviceMethodCache = new ConcurrentHashMap<>();
    final Call.Factory callFactory;
    final HttpUrl baseUrl;

    MyRetrofit(Call.Factory callFactory, HttpUrl baseUrl) {
        this.callFactory = callFactory;
        this.baseUrl = baseUrl;
    }

   
    /**
     * 构建者模式,将一个复杂对象的构建和它的表示分离,可以使使用者不必知道内部组成的细节。
     */
    public static final class Builder {
        private HttpUrl baseUrl;
        //Okhttp->OkhttClient
        private okhttp3.Call.Factory callFactory;  //null


        public Builder callFactory(okhttp3.Call.Factory factory) {
            this.callFactory = factory;
            return this;
        }

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

        public MyRetrofit build() {
            if (baseUrl == null) {
                throw new IllegalStateException("Base URL required.");
            }
            okhttp3.Call.Factory callFactory = this.callFactory;
            if (callFactory == null) {
                callFactory = new OkHttpClient();
            }

            return new MyRetrofit(callFactory, baseUrl);
        }
    }
}

构建者模式,之所以不需要关注内部细节,我们可以只设置我们关心的参数,这样的话我们必须在build方法里对不能为空的参数属性进行判断,如果为空就设置默认值,这样在使用的时候,就算我们什么都不设置,也能正常使用,而不至于报空指针

有了我们的Retrofit,我们得让他干活啊,我们得用它来产生我们的代理对象

2.2、用Retrofit产生Service的代理对象

public <T> T create(final Class<T> service) {
        return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //解析这个method 上所有的注解信息
                        ServiceMethod serviceMethod = loadServiceMethod(method);
                        //args:
                        return serviceMethod.invoke(args);
                    }
                });
    }

生成了我们的代理对象

代理对象执行方法,就会走InvocationHandler这个回调,会把我们要执行的网络请求方法,回调过来,我们就能获取到这个方法的所有接口的方法的所有注解信息和方法的参数注解信息,我们来解析一下上代码

2.3、ServiceMethod 解析Service接口方法的注解信息

这一步就是加一个请求的缓存,我们在执行多次同样的方法时,我们的请求参数的key和请求类型是固定的,只有请求参数的值是不一样的,所以我们要做一个缓存,来存储请求过的方法信息,

    private ServiceMethod loadServiceMethod(Method method) {
        //先不上锁,避免synchronized的性能损失
        ServiceMethod result = serviceMethodCache.get(method);
        if (result != null) return result;
        //多线程下,避免重复解析,
        synchronized (serviceMethodCache) {
            result = serviceMethodCache.get(method);
            if (result == null) {
                result = new ServiceMethod.Builder(this, method).build();
                serviceMethodCache.put(method, result);
            }
        }
        return result;
    }

加完缓存 我们就来写Service的Builder的build方法

     public ServiceMethod build() {

            /**
             * 1 解析方法上的注解, 只处理POST与GET
             */
            for (Annotation methodAnnotation : methodAnnotations) {
                if (methodAnnotation instanceof POST) {
                    //记录当前请求方式
                    this.httpMethod = "POST";
                    //记录请求url的path
                    this.relativeUrl = ((POST) methodAnnotation).value();
                    // 是否有请求体
                    this.hasBody = true;
                } else if (methodAnnotation instanceof GET) {
                    this.httpMethod = "GET";
                    this.relativeUrl = ((GET) methodAnnotation).value();
                    this.hasBody = false;
                }
            }


            /**
             * 2 解析方法参数的注解
             */
            int length = parameterAnnotations.length;
            parameterHandler = new ParameterHandler[length];
            for (int i = 0; i < length; i++) {
                // 一个参数上的所有的注解
                Annotation[] annotations = parameterAnnotations[i];
                // 处理参数上的每一个注解
                for (Annotation annotation : annotations) {
                    //todo 可以加一个判断:如果httpMethod是get请求,现在又解析到Filed注解,可以提示使用者使用Query注解
                    if (annotation instanceof Field) {
                        //得到注解上的value: 请求参数的key
                        String value = ((Field) annotation).value();
                        parameterHandler[i] = new ParameterHandler.FiledParameterHandler(value);
                    } else if (annotation instanceof Query) {
                        String value = ((Query) annotation).value();
                        parameterHandler[i] = new ParameterHandler.QueryParameterHandler(value);

                    }
                }
            }

            return new ServiceMethod(this);
        }

ParameterHandler的作用就是将我们Service方法的参数的key用一种顺序来把它存起来,我们再执行invok的时候再把参数的valueh和key给拼接起来

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

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

    static class FiledParameterHandler extends ParameterHandler {
        String key;

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

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

现在我们service的接口方法的注解信息就解析完了 然后就到了Retrofit的创建代理的 return serviceMethod.invoke(args);这一步了,这一步就是将我们的信息丢给Okhttp返回一个Call了

                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //解析这个method 上所有的注解信息
                        ServiceMethod serviceMethod = loadServiceMethod(method);
                        //args:
                        return serviceMethod.invoke(args);
                    }
                  

我们为什么执行invoke的时候会把方法的参数 args传下去,看我们的代码就知道了

2.4、编写serviceMethod的invoke方法返回一个call

 public Object invoke(Object[] args) {
        /**
         * 1  处理请求的地址与参数
         */
        for (int i = 0; i < parameterHandler.length; i++) {
            ParameterHandler handlers = parameterHandler[i];
            //handler内本来就记录了key,现在给到对应的value
            handlers.apply(this, args[i].toString());
        }

        //获取最终请求地址
        HttpUrl url;
        if (urlBuilder == null) {
            urlBuilder = baseUrl.newBuilder(relativeUrl);
        }
        url = urlBuilder.build();

        //请求体
        FormBody formBody = null;
        if (formBuild != null) {
            formBody = formBuild.build();
        }

        Request request = new Request.Builder().url(url).method(httpMethod, formBody).build();
        return callFactory.newCall(request);
    }

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

    //Post   把k-v 放到 请求体中
    public void addFiledParameter(String key, String value) {
        formBuild.add(key, value);
    }

这样我们就完成了对call的生产和返回,简易版的Retrofit就完事了!然后用Call执行enqueue方法!

3、总结

看到这里是不是脑袋瓜子嗡嗡的,我们再来捋一下思路, 首先有一个Service接口,然后我们的Retrofit通过动态代理来创建一个Service接口的代理对象serviceAPI,serviceAPI调用需要请求的方法,它会执行InvocationHandler的回调会把当前代理对象执行的方法传递过去, 然后我们ServiceMethod 对传过来的method通过反射来解析方法上的信息,同时将注解的key保存下来,然后再把传递过来的方法的参数丢到ServiceMethod的invoke中,在invoke里将参数的值也就是http请求的value和key进行拼接,再加上其他的信息比如请求方式等,返回一个call完成了Retrofit的功能!

image.png

以上是关于手撸简易版的Retrofit的主要内容,如果未能解决你的问题,请参考以下文章

99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个!|建议收藏!!!

10分钟手把手教你用Android手撸一个简易的个人记账App

10分钟手把手教你用Android手撸一个简易的个人记账App

Django——纯手撸简易web框架Django初识

手把手带你10分钟手撸一个简易的Markdown编辑器

手把手带你10分钟手撸一个简易的Markdown编辑器