Retrofit源码设计模式解析(上)
Retrofit通过注解的方法标记HTTP请求参数,支持常用HTTP方法,统一返回值解析,支持异步/同步的请求方式,将HTTP请求对象化,参数化。真正执行网络访问的是Okhttp,Okhttp支持HTTP&HTTP2,因此,使用Retrofit可以支持REST、HTTPS及SPDY。
行业内分析Retrofit的使用方法的文章已经比较丰富,这里不再赘述,如想了解这部分内容,请参考如下链接。
本文主要从设计模式的角度分享对Retrofit源码的一些理解。
- 外观模式
- 建造者模式
- 代理模式
- 简单工厂模式
- 工厂模式
- 抽象工厂模式
一、外观模式
在封装某些特定功能的子系统时,外观模式是一种很好的设计规范。即该子系统的外部与内部通信时通过一个统一的对象进行。Retrofit是整个库的一个入口类,Retrofit库的使用基本都是围绕着这个类。外观模式具有高内聚、低耦合的特性,对外提供简单统一的接口,隐蔽了子系统具体的实现、隔离变化。
Retrofit的外观模式的UML类图如下所示。
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; }
关于同步,这里有两点值得学习:
- 采用synchronized将锁加在serviceMethodCache上,而不是加到方法上。(直接synchronized加到方法可能会引起Dos,当然,你可以说客户端不用考虑这种问题);
- 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图如下:(图片来自互联网)
这里以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需要用抽象工厂模式,用工厂模式可以吗?如果用工厂模式,客户端需要怎么配置?