Android进阶宝典 -- 动态代理设计模式实现Retrofit框架

445 阅读10分钟

代理模式,在大家的日常开发工作中可能用不到,但是又随处可见,例如Retrofit网络框架就是采用动态代理设计模式实现的。那么代理设计模式能够解决什么问题呢?从架构层面看,代理模式的思想是如何运用的,接下来我们将会对两种代理模式做详细的介绍。

1 静态代理模式

在介绍动态代理模式之前,我们首先看一下静态代理模式。静态代理模式旨在调用层和实现层之间加一层隔离

image.png

调用层不需要关心实现层是如何实现的,只需要通知隔离层需要什么,后续的执行就由隔离层分发处理,举个简单的例子。

interface IHttpRequest {
    fun post()
    fun get()
}

1.1 静态代理模式实现

在网络请求框架的演变过程中,出现过Volley、OkHttp、Retrofit等优秀的开源框架;如果不采用静态代理设计模式,那么每次更换框架,需要将原先的实现全部干掉,换成新的框架实现,这就违背了开闭原则,每次需要修改实现层的代码。

class HttpRequestUtils : IHttpRequest {
    override fun post() {
//        Log.e("TAG","实现Volley的post请求")
        Log.e("TAG","实现OkHttp的post请求")
    }

    override fun get() {
//        Log.e("TAG","实现Volley的get请求")
        Log.e("TAG","实现OkHttp的get请求")
    }
}

那么静态代理是如何实现的呢?首先针对每一个框架,都有自己的实现,并实现了隔离层接口。

class VolleyRequest : IHttpRequest {
    override fun post() {
        Log.e("TAG","Volley post")
    }

    override fun get() {
        Log.e("TAG","Volley get")
    }
}
class OkHttpRequest : IHttpRequest {
    override fun post() {
        Log.e("TAG","OkHttpRequest post")
    }

    override fun get() {
        Log.e("TAG","OkHttpRequest get")
    }
}

那么代理类的作用就是持有隔离层接口,暴露给调用层一个setRequest方法,可以设置请求的方式

class HttpRequestUtils : IHttpRequest {

    private var htIHttpRequest: IHttpRequest? = null
    
    fun setRequest(htIHttpRequest: IHttpRequest){
        this.htIHttpRequest = htIHttpRequest
    }
    
    override fun post() {
        htIHttpRequest?.post()
    }

    override fun get() {
        htIHttpRequest?.get()
    }
    
    companion object{
        val instance:HttpRequestUtils by lazy { 
            HttpRequestUtils()
        }
    }
}

在调用层可以灵活配置需要的网络请求框架,并且可以放在Application中做全局的初始化

HttpRequestUtils.instance.setRequest(VolleyRequest())
HttpRequestUtils.instance.post()

1.2 静态代理的弊端

我们看到,代理类只是持有了一个隔离层接口,如果有2个、3个接口,那么代理类就需要处理多个接口,隔离层会变得臃肿起来。

class HttpRequestUtils : IHttpRequest {

    private var htIHttpRequest: IHttpRequest? = null
    private var otherRequest: IOtherRequest? = null
    
    fun setOtherRequest(otherRequest: IHttpRequest){
        this.otherRequest = otherRequest
    }
    //...... 如果10个接口就会膨胀下去
    
    fun setRequest(htIHttpRequest: IHttpRequest){
        this.htIHttpRequest = htIHttpRequest
    }
    
    override fun post() {
        htIHttpRequest?.post()
    }

    override fun get() {
        htIHttpRequest?.get()
    }
    
    companion object{
        val instance:HttpRequestUtils by lazy { 
            HttpRequestUtils()
        }
    }
}

或者使用多个代理类,如果有10个接口就需要创建10个代理类,有10个这种HttpRequestUtils代理类,这种情况下,静态代理设计模式就不再适用了,如果我们想一个代理类就管理全部的接口,动态代理模式就出现了。

2 动态代理模式

2.1 动态代理参数

Proxy.newProxyInstance(
    classLoader, arrayOf(
        IHttpRequest::class.java
    )
) { obj, method, objs ->
    
}

调用Proxy的newProxyInstance方法能够实现动态代理,Proxy是反射中的类,因此我们可以知道动态代理就是通过反射实现的,其中有3个参数:

newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

loader:类加载器
interfaces:要代理的接口集合,也就是说无论多少个接口,都能通过代理实现
InvocationHandler:可以看做是回调,每次方法的调用都会走这个回调

Params:
//proxy – the proxy instance that the method was invoked on
//method – the Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method object will be the //interface that the method was declared in, which may be a superinterface of the //proxy interface that the proxy class inherits the method through.
//args – an array of objects containing the values of the arguments passed in the //method invocation on the proxy instance, or null if interface method takes no //arguments. Arguments of primitive types are wrapped in instances of the //appropriate primitive wrapper class, such as java.lang.Integer or //java.lang.Boolean.
public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable;

之前在Hook AMS的时候也提到过,如果系统源码是Java写的,动态代理的代码最好用Java写,因为Kotlin和Java有些类型还是有差别的,所以之前写Kotlin动态代理老是会有问题,先用Java实现了。

VolleyRequest request = new VolleyRequest();

Object obj = Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
        new Class[]{IHttpRequest.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.e("TAG","method"+method);
                return method.invoke(request,args);
            }
        });
((IHttpRequest) obj).post();

回到正题,通过newProxyInstance创建出代理对象之后,可以将该代理对象强转成某个隔离层接口,调用接口方法后,会走InvocationHandler这个回调,在这个回调中,就可以调用具体类的方法。

2022-10-08 12:25:27.168 14116-14116/com.lay.mvi E/TAG: methodpublic abstract void com.lay.mvi.api.IHttpRequest.post()
2022-10-08 12:25:27.168 14116-14116/com.lay.mvi E/TAG: Volley post

所以我们可以看到,在这个方法执行之前,可以做一些处理,例如修改方法的入参,Hook技术就是采用了这点。

2.2 动态代理实现

所以相对于静态代理,动态代理这边同样是一个代理类,但是需要代理不同的接口

public class MyProxy {

    private static MyProxy proxy = new MyProxy();

    public static MyProxy getInstance() {
        return proxy;
    }

    public <T> T create(Class<T> clazz, Object obj) {
        return (T) Proxy.newProxyInstance(MyProxy.class.getClassLoader(),
                new Class[]{clazz}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        return method.invoke(obj, args);
                    }
                });
    }
}

create方法是一个泛型方法,第一个参数传入一个接口类的Class对象,第二个参数可传入接口的具体实现,最终返回的就是一个代理对象。

val create = MyProxy.getInstance().create(IHttpRequest::class.java,VolleyRequest())
create.post()

在调用层传入对应的接口,就能使用一个代理类完成全部接口的代理。

3 采用动态代理实现Retrofit

我们这里不是实现整个Retrofit框架,因为我们知道Retrofit自身并没有网络请求的能力,而是把网络请求交给了OkHttp,Retrofit只是利用注解、动态代理实现了请求接口的封装。

fun getApi(): ApiService {

    val builder = OkHttpClient.Builder()
        .build()
    //Retrofit的封装
    val retrofit = Retrofit.Builder()
        .baseUrl("http://apis.juhe.cn/simpleWeather/")
        .client(builder)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        //动态代理
    return retrofit.create(ApiService::class.java)
}

我们看下create方法的源码,里面正是用到了我们前面使用到的动态代理

public <T> T create(final Class<T> service) {
  validateServiceInterface(service);
  return (T)
      Proxy.newProxyInstance(
          service.getClassLoader(),
          new Class<?>[] {service},
          new InvocationHandler() {
            private final Platform platform = Platform.get();
            private final Object[] emptyArgs = new Object[0];

            @Override
            public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
                throws Throwable {
              // If the method is a method from Object then defer to normal invocation.
              if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
              }
              args = args != null ? args : emptyArgs;
              return platform.isDefaultMethod(method)
                  ? platform.invokeDefaultMethod(method, service, proxy, args)
                  : loadServiceMethod(method).invoke(args);
            }
          });
}

所以ApiService就是一个接口,里面采用注解的形式封装了网络请求的类型、参数等

interface ApiService {
    @GET
    suspend fun getWeather(
        @Url url: String,
        @Query("city") city: String,
        @Query("key") key: String
    ): Weather
}

3.1 Retrofit Builder

首先我们先将Retrofit对象的创建定义出来,就是一个简单的建造者设计模式。

public class MyRetrofit {

    private String mUrl;
    private OkHttpClient client;
    private Converter.Factory factory;


    public MyRetrofit(String url, OkHttpClient client, Converter.Factory factory) {
        this.mUrl = url;
        this.client = client;
        this.factory = factory;
    }


    class Builder {

        private String baseUrl;

        //这部分先用Retrofit自带的
        private OkHttpClient client;
        private Converter.Factory factory;

        public Builder setBaseUrl(String url){
            this.baseUrl = url;
            return this;
        }
        
        public Builder client(OkHttpClient client){
            this.client = client;
            return this;
        }
        
        public Builder addConvertFactory(Converter.Factory factory){
            this.factory = factory;
            return this;
        }

        public MyRetrofit build(){
            if (baseUrl.isEmpty()) {
                throw new IllegalArgumentException("BASE URL must be not empty");
            }
            return new MyRetrofit(baseUrl, client, factory);
        }
    }
}

3.2 注解定义

在Retrofit中,定义了很多注解,像@POST、@GET等,其实很多注解我们容易搞混,既然我们自己定义注解,就可以简单明了一些。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) //在方法上使用
public @interface MyGet {
    String url() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyPost {
    String url();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) //标记参数
public @interface MyGetParams {
    String value();
}

我们定义了两个请求注解@MyGet和@MyPost,用于区分GET请求和POST请求;@MyGetParams注解用于标注GET请求的请求参数,我们就先以GET请求为例。

public interface MyApiService {

    @MyGet(url = "query")
    Call getWeather(
            @MyGetParams("city") String city,
            @MyGetParams("key") String key
    );
}

3.3 create方法

其实在2.2小节中,我们有写过一个泛型方法就是类似于Retrofit中的create方法,当然这里我们不仅仅是调用一次方法,还需要获取注解。

public <T> T create(Class<T> clazz) {

    return (T) Proxy.newProxyInstance(MyRetrofit.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            ServiceMethod serviceMethod = loadServiceMethod(method);
            return null;
        }
    });
}

private ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result = methodMap.get(method);
    if (result != null) return result;

    synchronized (methodMap) {
        result = methodMap.get(method);
        if (result == null) {
            result = new ServiceMethod.Builder()
                    .setMethod(method)
                    .setRetrofit(this)
                    .build();
            methodMap.put(method, result);
        }
    }
    return result;
}

create方法还是接收了一个接口类,通过动态代理的方式创建了代理类,首先第一步就是解析注解。因为在项目中,接口可能会被多次请求,不需要每次请求接口的时候都需要解析注解,而是在第一次的时候解析完成存储在HashMap中。

3.3.1 解析方法注解

接下来,我们需要解析注解,注解主要分为方法上的注解和参数中的注解,Method类中都有对应的API可以调用。

public class ServiceMethod {

    private MyRetrofit myRetrofit;
    private Method method;

    public ServiceMethod(MyRetrofit retrofit,Method method){
        this.myRetrofit = retrofit;
        this.method = method;
        //解析注解
        processAnnotation(method);
    }

    //解析注解
    private void processAnnotation(Method method) {
        
    }

    public static class Builder{

        public Builder(){}
        private MyRetrofit myRetrofit;
        private Method method;

        public Builder setRetrofit(MyRetrofit retrofit){
            this.myRetrofit = retrofit;
            return this;
        }
        public Builder setMethod(Method method){
            this.method = method;
            return this;
        }
        public ServiceMethod build(){
            return new ServiceMethod(myRetrofit,method);
        }
    }
}

首先解析方法注解,调用Method的getDeclaredAnnotations,获取全部的注解,因为在一个方法上可以使用多个注解,因此拿到的是一个数组。

Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
for (Annotation annotation :
        declaredAnnotations) {
    Log.e("TAG", "注解 ===>" + annotation);
    //判断是get还是post请求
    this.realUrl = doProcessRequestBridge(annotation);
    Log.e("TAG", "请求url ===>" + realUrl);
}

对每个注解类型做判断,如果是GET请求,记录当前Method的请求类型,然后和BaseUrl做参数拼接,记录真正请求的url

private String doProcessRequestBridge(Annotation annotation) {

    StringBuffer buffer = new StringBuffer(myRetrofit.getBaseUrl());

    if (annotation instanceof MyGet) {
        this.requestType = REQUEST.GET;
        //如果是get请求,拼接url
        String url = ((MyGet) annotation).url();
        buffer.append(url);
        return buffer.toString();
    } else {
        this.requestType = REQUEST.POST;
        //如果是post请求
        String url = ((MyPost) annotation).url();
        buffer.append(url);
        return buffer.toString();
    }
}

3.3.2 解析参数注解

通过getParameterAnnotations方法,能够获取全部参数的注解,这里拿到了是一个二维数组,是因为请求的参数可能会有多个,每个参数还可能承载多个注解

//解析参数注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Log.e("TAG", "parameterAnnotations" + parameterAnnotations);
for (int i = 0; i < parameterAnnotations.length; i++) {
    Annotation[] parameterAnnotation = parameterAnnotations[i];
    //获取参数上的全部注解
    for (Annotation annotation :
            parameterAnnotation) {
        doProcessParamsBridge(annotation);
    }
}
//list -- [city, key]

所以首先获取每个参数,然后处理每个参数上的注解,并记录当前方法全部的请求key

private void doProcessParamsBridge(Annotation annotation) {
    if (annotation instanceof MyGetParams) {

        if (this.requestType == REQUEST.POST) {
            throw new IllegalArgumentException("POST请求不能使用@MyGetParams注解");
        }
        String key = ((MyGetParams) annotation).value();
        //记录请求的参数key
        keyList.add(key);
    }
}

3.3.3 方法执行

当所有的参数执行完成之后,就需要正式执行这个方法了

public <T> T create(Class<T> clazz) {

    return (T) Proxy.newProxyInstance(MyRetrofit.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            ServiceMethod serviceMethod = loadServiceMethod(method);
            return serviceMethod.invoke(args);
        }
    });
}

所以我们还需要实现一个invoke方法,在这个方法中传入的参数,就是之前注解解析时,与key一一对应的。

public Object invoke(Object[] args) {
    if (args.length == 0) {
        return null;
    }

    RequestBody requestBody = null;
    String requestType = "GET";
    HttpUrl.Builder builder = HttpUrl.parse(this.realUrl).newBuilder();

    if (this.requestType == "GET") {
        //拼接字符串
        StringBuffer buffer = new StringBuffer(this.realUrl);
        buffer.append("?");
        for (int i = 0; i < keyList.size(); i++) {
            String key = keyList.get(i);
            builder.addQueryParameter(key,args[i].toString());
        }
    } else {
        //创建请求体
        requestBody = new FormBody.Builder().build();
    }


    Request request = new Request.Builder()
            .url(builder.build())
            //如果是get请求,不需要RequestBody
            .method(requestType,requestBody)
            .build();

    return myRetrofit.client.newCall(request);
}

这里是采用了HttpUrl.Builder来拼接url,然后创建一个Request请求,使用OkHttp发起一个网络请求,最终返回了请求结果。

3.4 调用

在应用端的调用和Retrofit一致

val builder = OkHttpClient.Builder()
    .build()

val retrofit = MyRetrofit.Builder()
    .client(builder)
    .setBaseUrl("http://apis.juhe.cn/simpleWeather/")
    .addConvertFactory(GsonConverterFactory.create())
    .build()
retrofit.create(MyApiService::class.java)
    .getWeather("北京","申请的聚合数据key")
    .enqueue(object : okhttp3.Callback{
        override fun onFailure(call: okhttp3.Call, e: IOException) {
            Log.e("TAG", "call $call")
        }

        override fun onResponse(call: okhttp3.Call, response: okhttp3.Response) {
            Log.e("TAG", "call ${response.body()?.string()}")
        }

    })
call {"reason":"查询成功!","result":{"city":"北京","realtime":{"temperature":"12","humidity":"84","info":"阴","wid":"02","direct":"西风","power":"1级","aqi":"92"},"future":[{"date":"2022-10-08","temperature":"9\/16℃","weather":"小雨转多云","wid":{"day":"07","night":"01"},"direct":"西南风转西北风"},{"date":"2022-10-09","temperature":"7\/16℃","weather":"多云转晴","wid":{"day":"01","night":"00"},"direct":"西北风"},{"date":"2022-10-10","temperature":"3\/19℃","weather":"晴","wid":{"day":"00","night":"00"},"direct":"西北风"},{"date":"2022-10-11","temperature":"6\/19℃","weather":"晴","wid":{"day":"00","night":"00"},"direct":"西南风"},{"date":"2022-10-12","temperature":"9\/19℃","weather":"晴转多云","wid":{"day":"00","night":"01"},"direct":"南风转北风"}]},"error_code":0}

此次设计的Retrofit只是做了注解和动态代理设计模式,像类型转换,使用Gson将json字符串转为实体类并没有去做,其实Retrofit核心就是动态代理和注解处理,而请求是OkHttp干的事

GitHub地址:github.com/LLLLLaaayyy…