Retrofit源码设计模式解析(上)

Posted Young

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Retrofit源码设计模式解析(上)相关的知识,希望对你有一定的参考价值。

Retrofit通过注解的方法标记HTTP请求参数,支持常用HTTP方法,统一返回值解析,支持异步/同步的请求方式,将HTTP请求对象化,参数化。真正执行网络访问的是Okhttp,Okhttp支持HTTP&HTTP2,因此,使用Retrofit可以支持REST、HTTPS及SPDY。

行业内分析Retrofit的使用方法的文章已经比较丰富,这里不再赘述,如想了解这部分内容,请参考如下链接。

用 Retrofit 2 简化 HTTP 请求

Retrofit 源码解析

本文主要从设计模式的角度分享对Retrofit源码的一些理解。

  1. 外观模式
  2. 建造者模式
  3. 代理模式
  4. 简单工厂模式
  5. 工厂模式
  6. 抽象工厂模式

一、外观模式

在封装某些特定功能的子系统时,外观模式是一种很好的设计规范。即该子系统的外部与内部通信时通过一个统一的对象进行。Retrofit是整个库的一个入口类,Retrofit库的使用基本都是围绕着这个类。外观模式具有高内聚、低耦合的特性,对外提供简单统一的接口,隐蔽了子系统具体的实现、隔离变化。

Retrofit的外观模式的UML类图如下所示。

image

Retrofit对客户端模块(Client1、Client2……)提供统一接口,Retrofit类内部封装了ServiceMethod、CallAdapter和Converter等组件。并且,CallAdapter和Converter都是抽象为接口,用户可以扩展自定义的实现。正如官方文档中的示例:

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

附:举个栗子,说明下外观模式在封装条形码/二维码扫描功能时的应用。

android中的条形码/二维码扫描功能通常会基于zxing库进行封装,定义CaptureActivity,统一提供扫码功能,并返回扫描结果。客户端使用该封装只需两步:首先,通过Intent启动CaptureActivity;然后,在onActivityResult中处理扫描结果。

Intent intent = new Intent(MainActivity.this, CaptureActivity.class);
startActivityForResult(intent, REQUESTCODE);
if (requestCode == REQUESTCODE && resultCode == RESULT_OK) {
    Bundle bundle = data.getExtras();
    String scanResult = bundle.getString(CaptureActivity.RESULT);
    helloWorld.setText(scanResult);
}

客户端不需要处理任何跟摄像头控制、调焦、图片处理、条形码解析等相关的问题。CaptureActivity提供了所有扫描相关的功能。

二、建造者模式

设计模式分为三种类型:创建型模式、结构型模式和行为型模式。建造者模式属于创建型模式,将构建复杂对象的过程和它的部件解耦,使构建过程和部件的表示隔离。Retrofit内部包含Retrofit.Builder,Retrofit包含的域都能通过Builder进行构建。经典设计模式(《设计模式:可复用面向对象软件的基础》)中建造者模式有四种角色:

  • Product产品类——该类为一般为抽象类,定义Product的公共属性配置;
  • Builder建造类——该类同样为抽象类,规范Product的组建,一般由子类实现具体Product的构建过程;
  • ConcreteBuilder实际建造类——继承自Builder,构建具体的Product;
  • Director组装类——统一组装过程。

在Retrofit类中,Retrofit直接对应Product,并没有基于抽象Product进行扩展;Retrofit.Builder对应ConcreteBuilder,也没有基于抽象Builder进行扩展,同时省略了Director,并在Retrofit.Builder每个setter方法都返回自身,使得客户端代码可以链式调用,整个构建过程更加简单。

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

举个栗子:笔者基于Retrofit封装适合自身业务的库时,由于需要配置基础URL、默认超时时间、拦截器以及是否添加转换器等,也采用相同的建造者模式。

public class NetWork {

    // 基础URL设置
    private String baseUrl;
    // 默认超时时间
    private long timeout;

    /**
     * NetWork构建者
     */
    public static class Builder {
        // 基础URL设置
        private String baseUrl;
        // 默认超时时间
        private long timeout = 5;

        /**
         * baseUrl为必填项
         *
         * @param baseUrl 基础Url
         */
        public Builder(String baseUrl) {
            this.baseUrl = baseUrl;
        }

        public Builder timeout(long timeout) {
            this.timeout = timeout;
            return this;
        }

        public NetWork build() {
            return new NetWork(this);
        }
    }

    /**
     * 构造器
     *
     * @param builder 构造builder
     */
    private NetWork(Builder builder) {
        this.baseUrl = builder.baseUrl;
        this.timeout = builder.timeout;
    }

三、代理模式

代理模式属于上述提到的结构型模式。当无法或不想直接访问某个对象,或者访问某个对象比较复杂的时候,可以通过一个代理对象来间接访问,代理对象向客户端提供和真实对象同样的接口功能。经典设计模式中,代理模式有四种角色:

  • Subject抽象主题类——申明代理对象和真实对象共同的接口方法;
  • RealSubject真实主题类——实现了Subject接口,真实执行业务逻辑的地方;
  • ProxySubject代理类——实现了Subject接口,持有对RealSubject的引用,在实现的接口方法中调用RealSubject中相应的方法执行;
  • Cliect客户端类——使用代理对象的类。

代理模式分为静态代理和动态代理,严格按照上述角色定义编写的代码属于静态代理,即在代码运行前ProxySubject代理类的class编译文件就已存在。Retrofit使用的是动态代理,是通过反射机制来动态生成方法接口的代理对象的。动态代理的实现是通过JDK提供的InvocationHandler接口,实现该接口重写其调用方法invoke。

public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
        eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
            private final Platform platform = Platform.get();
            @Override public Object invoke(Object proxy, Method method, 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);
            }
            if (platform.isDefaultMethod(method)) {
                return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
        }
    });
}

Proxy.newProxyInstance返回接口的动态代理类,InvocationHandler的invoke方法处理method分为三种情况:1)Object的方法,直接返回Object的实现;2)判断是否Java8支持的DefaultMethod;3)创建OkHttpCall,通过ServiceMethod转换为接口的动态代理类。

使用Retrofit的客户端通过create方法获取自定义HTTP请求的动态代理类,是客户端代码中最重要的部分之一。这里有三个重要组件:

  • ServiceMethod
  • OKHttpCall
  • ServiceMethod.callAdapter

ServiceMethod用于处理Api Service上定义的注解,参数等,得到这个ServiceMethod之后,传给OkHttpCall,这个OkHttpCall就是对Okhttp的网络请求封装的一个类。

ServiceMethod loadServiceMethod(Method method) {
    ServiceMethod result;
    synchronized (serviceMethodCache) {
        result = serviceMethodCache.get(method);
        if (result == null) {
            result = new ServiceMethod.Builder(this, method).build();
            serviceMethodCache.put(method, result);
        }
    }
    return result;
}

关于同步,这里有两点值得学习:

  1. 采用synchronized将锁加在serviceMethodCache上,而不是加到方法上。(直接synchronized加到方法可能会引起Dos,当然,你可以说客户端不用考虑这种问题);
  2. serviceMethodCache是用于缓存HTTP请求方法的,初始方法采用LinkedHashMap而不是普通的HashMap。
private final Map<Method, ServiceMethod> serviceMethodCache = new LinkedHashMap<>();

LinkedHashMap中有一个字段accessOrder,表示是否按照访问顺序进行重排序。默认为false,表示按插入时间顺序排序,如果设置为true,则进行重排序。最近最少访问的元素会被放到队尾,最先删除,而最常访问的元素,则放到队头,最后删除。

把ServiceMethod传给OkHttpCall实际上就是把网络接口所需要的URL,参数等条件传给了OkHttpCall,就可以进行网络请求了。ServiceMethod中如果没有配置CallAdapter,则使用默认的DefaultCallAdapterFactory, 得到的结果是Call<?>。

回到正题,你可能已经发现,create方法采用的代理模式和正常的代理模式并不一样,正常的代理模式只是对真实对象的一层控制,这个真实对象是实现对应的接口的,而这里并没有真实的对象,它把方法调用最终全部转发到OKHttp了。

四、简单工厂模式

本文后面的部分将集中在简单工厂模式、工厂模式和抽象工厂模式,它们都属于创建型模式,其主要功能都是将对象的实例化部分抽取出来。简单工厂模式也称为静态工厂模式,包含三种角色:

  • Factory工厂角色——负责实现创建所有实例的内部逻辑;
  • Product抽象产品角色——创建的所有对象的父类,负责描述所有实例所共有的公共接口;
  • ConcreteProduct具体产品角色——继承自Product,负责具体产品的创建。

简单工厂模式是一种很常见、很简单的设计模式,以Platform类为例,其首先包含静态域PLATFORM,并通过静态返回供客户端调用。

private static final Platform PLATFORM = findPlatform();

static Platform get() {
    return PLATFORM;
}

findPlatform其实就是一个静态工厂方法,根据Class.forName是否抛出ClassNotFoundException来判断不同的平台。

private static Platform findPlatform() {
    try {
        Class.forName("android.os.Build");
        if (Build.VERSION.SDK_INT != 0) {
            return new Android();
        }
    } catch (ClassNotFoundException ignored) {
    }
    try {
        Class.forName("java.util.Optional");
        return new Java8();
    } catch (ClassNotFoundException ignored) {
    }
    try {
        Class.forName("org.robovm.apple.foundation.NSObject");
        return new ios();
    } catch (ClassNotFoundException ignored) {
    }
    return new Platform();
}

而Android、Java8、IOS相当于ConcreteProduct的角色,继承自抽象产品类Platform。

Java8:

static class Java8 extends Platform {}

Android:

static class Android extends Platform {}

IOS:

static class IOS extends Platform {}

五、工厂模式

上述简单工厂模式中的工厂方法类只有一个Factory,仍以Platform为例,如果在增加一种平台:Windows Phone,那么就需要修改findPlatform方法,添加Windows Phone类的创建。

这种修改模式不符合“开闭原则”,即对扩展开放,对修改封闭。本着可扩展的原则,抽象Factory类的公共部分为抽象类,然后不同的平台工厂继承自抽象的Factory。需要不用的平台就调用不同的工厂方法,这就是工厂模式。即对简单工厂中的工厂类进行抽象:

  • Factory抽象工厂类——负责工厂类的公共部分;
  • ConcreteFactory具体工厂类——继承自Factory,实现不同特性的工厂。

如果按照工厂模式,通过PlatformFactory类抽象工厂方法,那么大概会是这样:

public abstract class PlatformFactory {
    abstract Platform findPlatform();
}

Android、Java8、IOS或者可能新增的Windows Phone工厂继承自PlatformFactory。

public class AndroidFactory extends PlatformFactory {

    @Override
    Platform findPlatform() {
        try {
            Class.forName("android.os.Build");
            if (Build.VERSION.SDK_INT != 0) {
                return new Android();
            }
        } catch (ClassNotFoundException ignored) {
        }
        return new Platform();
    }
}

客户端需要不同的平台对象就调用不同的工厂,但客户端如果调用错误,比如在Android上调用了IOS的工厂,那么就会得到一个Platform的实例,这并不符合要求,这种写法增加了客户端的难度,同时,需要引入抽象层,增加多个具体工厂类,维护成本也更大。

所以,在使用工厂设计模式时,一定需要衡量利弊,在特定的场景选择最合适的设计模式。那么,Retrofit中使用工厂模式的经典例子又是什么呢?CallAdapter!

public interface CallAdapter<T> {

    Type responseType();
    <R> T adapt(Call<R> call);
 
    abstract class Factory {
        public abstract CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);

        protected static Type getParameterUpperBound(int index, ParameterizedType type) {
            return Utils.getParameterUpperBound(index, type);
        }

        protected static Class<?> getRawType(Type type) {
            return Utils.getRawType(type);
        }
    }
}

CallAdapter是什么呢?见名知义,对Call进行适配,这里涉及到适配器模式,下节会着重说明。这里关注CallAdapter.Factory,CallAdapter.Factory对应上述角色中的Factory抽象工厂类,包含两个静态工具方法getParameterUpperBound、getRawType和抽象方法get。

get方法返回不同类型的CallAdapter,RxJavaCallAdapterFactory返回CallAdapter<Observable<?>>,DefaultCallAdapterFactory返回CallAdapter<Call<?>>。

public final class RxJavaCallAdapterFactory extends CallAdapter.Factory {

    // 省略代码
    @Override
    public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    
        // 省略代码
        CallAdapter<Observable<?>> callAdapter = getCallAdapter(returnType, scheduler);
        // 省略代码
        return callAdapter;
    }

    private CallAdapter<Observable<?>> getCallAdapter(Type returnType, Scheduler scheduler) {
        // 省略代码
    }
}

如果需要增加新的CallAdapter,继承自CallAdapter.Factory,覆盖get方法即可。符合面向对象软件设计的“开闭原则”。

六、抽象工厂模式

在配置Retrofit时,除了上述提到的CallAdapter,还需要addConverterFactory。Retrofit调用Okhttp时,将请求内容由T转换为okhttp3.RequestBody,将返回内容由okhttp3.ResponseBody转换为T,Converter是就是负责转换的类。Retrofit官方文档中给出了多种不同实现的转换器类,如下:

Gson: com.squareup.retrofit2:converter-gson
Jackson: com.squareup.retrofit2:converter-jackson
Moshi: com.squareup.retrofit2:converter-moshi
Protobuf: com.squareup.retrofit2:converter-protobuf
Wire: com.squareup.retrofit2:converter-wire
Simple XML: com.squareup.retrofit2:converter-simplexml
Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars

其中包含JSON转换的Gson、Jackson,负责PB解析的Protobuf,负责XML解析的Simple XML,这些类具有相同点,均继承自Converter.Factory。

public interface Converter<F, T> {
    T convert(F value) throws IOException;

    abstract class Factory {
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, 
               Retrofit retrofit) {
            return null;
        }

        public Converter<?, RequestBody> requestBodyConverter(Type type, 
               Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
            return null;
        }

        public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            return null;
        }
    }
}

这里注意下Converter.Factory与CallAdapter.Factory的区别,CallAdapter.Factory只有一个抽象的get方法返回CallAdapter<?>,Converter.Factory有三个方法,分别返回Converter<ResponseBody, ?>、Converter<?, RequestBody>和Converter<?, String>。

相比于工厂模式,具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象,例如:上述Converter.Factory需要同时提供请求内容和返回内容的转换类,这时,就需要考虑抽象工厂模式。抽象工厂模式同样包含四种角色:

  • AbstractFactory:抽象工厂
  • ConcreteFactory:具体工厂
  • AbstractProduct:抽象产品
  • Product:具体产品

标准抽象工厂模式的UML图如下:(图片来自互联网)

AbatractFactory

这里以GsonConverterFactory为例进行说明。GsonConverterFactory对应ConcreteFactory具体工厂,表示Gson转换类的工厂,GsonConverterFactory继承自AbstractFactory抽象工厂——Converter.Factory,重写了requestBodyConverter方法和responseBodyConverter方法,相当于上图中的createProductA和createProductB。

public final class GsonConverterFactory extends Converter.Factory {
    // 省略代码
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonResponseBodyConverter<>(gson, adapter);
    }

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
        TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
        return new GsonRequestBodyConverter<>(gson, adapter);
    }
}

这里GsonRequestBodyConverter对应ProductA,GsonResponseBodyConverter对应ProductB。

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    // 省略代码
}
final class GsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
    // 省略代码
}

Converter<T, RequestBody>对应抽象产品AbstractProductA,Converter<ResponseBody, T>对应抽象产品AbstractProductB。

最后思考下:为什么Converter.Factory需要用抽象工厂模式,用工厂模式可以吗?如果用工厂模式,客户端需要怎么配置?

以上是关于Retrofit源码设计模式解析(上)的主要内容,如果未能解决你的问题,请参考以下文章

站在巨人的肩膀上 -- Retrofit源码解析

站在巨人的肩膀上 -- Retrofit源码解析

Retrofit源码设计模式解析(下)

基于Retrofit+RxJava的Android分层网络请求框架

Retrofit源码解析-动态代理

Retrofit源码解析-动态代理