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类图如下所示。

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图如下:(图片来自互联网)

这里以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源码设计模式解析(上)的更多相关文章

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

    本文将接着<Retrofit源码设计模式解析(上)>,继续分享以下设计模式在Retrofit中的应用: 适配器模式 策略模式 观察者模式 单例模式 原型模式 享元模式 一.适配器模式 在上 ...

  2. Retrofit源码解析(上)

    简介Retrofit是Square公司开发的一款针对Android网络请求的框架,官网地址http://square.github.io/retrofit/ ,在官网上有这样的一句话介绍retrofi ...

  3. 还怕问源码?Github上神级Android三方源码解析手册,已有7.6 KStar

    或许对于许多Android开发者来说,所谓的Android工程师的工作"不过就是用XML实现设计师的美术图,用JSON解析服务器的数据,再把数据显示到界面上"就好了,源码什么的,看 ...

  4. spring源码深度解析— IOC 之 默认标签解析(上)

    概述 接前两篇文章  spring源码深度解析—Spring的整体架构和环境搭建  和  spring源码深度解析— IOC 之 容器的基本实现 本文主要研究Spring标签的解析,Spring的标签 ...

  5. [置顶] 【Android实战】----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

    一.简介 接上一篇[Android实战]----基于Retrofit实现多图片/文件.图文上传中曾说非常想搞明白为什么Retrofit那么屌.最近也看了一些其源码分析的文章以及亲自查看了源码,发现其对 ...

  6. Retrofit源码分析(一)

    1.基本用法 创建接口 public interface GitHubService { @GET("users/{user}/repos") Observable<List ...

  7. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  8. Kotlin系列之序列(Sequences)源码完全解析

    Kotlin系列之序列(Sequences)源码完全解析 2018年06月05日 22:04:50 mikyou 阅读数:179 标签: Kotlin序列(sequence)源码解析Androidja ...

  9. Tomcat处理HTTP请求源码分析(上)

    Tomcat处理HTTP请求源码分析(上) 作者 张华 发布于 2011年12月8日 | 8 讨论 分享到: 微博 微信 Facebook Twitter 有道云笔记 邮件分享 稍后阅读 我的阅读清单 ...

随机推荐

  1. 快乐的JS正则表达式(三)

    ?的用途. 小任务:匹配一段网址如var str = "http://www.123.com/";注意http也可以是https var str = "http://i. ...

  2. epoll源码实现分析[整理]

    epoll用法回顾 先简单回顾下如何使用C库封装的3个epoll相关的系统调用.更详细的用法参见http://www.cnblogs.com/apprentice89/archive/2013/05/ ...

  3. 自定义能够for each的类,C#,Java,C++,C++/cli的实现方法

    自定义类能够被for each,应该算是个老生常谈的话题了,相关的资料都很多,不过这里整理总结主流语言的不同实现方式,并比较部分细节上的差异. 第一种语言,也是实现起来最简单的Java语言.在Java ...

  4. "浅谈Android"第一篇:Android系统简介

    近来,看了一本书,名字叫做<第一行代码>,是CSDN一名博主写的,一本Android入门级的书,比较适合新手.看了书之后,有感而发,想来进行Android开发已经有一年多了,但欠缺系统化的 ...

  5. Django--models表操作

    需求 models对表的增删改查 知识点 1.基础操作 1.1  增 方法一 1 models.Tb1.objects.create(c1='xx', c2='oo')  #增加一条数据 1 2 di ...

  6. SQL SERVER2008及以上版本数据库自动备份的三种方法

    方法一:创建一个维护计划对数据库进行备份 方法二:创建一个SQL作业对数据库进行备份 方法三:创建WINDOWS任务计划对数据库进行备份 方法一与方法二其实原理基本相同,都必需开启SQL代理服务,都会 ...

  7. Use the PDFs below or the HTML contents to the left to install and configure P6 EPPM and its additional components.

    Welcome to Your Documentation   Use the PDFs below or the HTML contents to the left to install and c ...

  8. log4net日志记录

    这里是接着上一篇来优化的,上篇:ASP.NET MVC中错误日志信息记录 log4Net是用来记录日志的,可以将程序运行过程中的信息输出到一些地方(文件,数据库,EventLog等),日志就是程序的黑 ...

  9. Vs2012出现停止工作问题的解决方法

    我的VS2012总是出现问题,打开项目会,更改移动控件位置也会,后来在网上找到了解决方法 这是出现问题

  10. MySQL更新优化

    通常情况下,当访问某张表的时候,读取者首先必须获取该表的锁,如果有写入操作到达,那么写入者一直等待读取者完成操作(查询开始之后就不能中断,因此允许读取者完成操作).当读取者完成对表的操作的时候,锁就会 ...