OkHttp框架中拦截器设计精髓之责任链模式

来源:互联网 发布:供应链网络优化 编辑:程序博客网 时间:2024/05/14 08:30

OkHttp框架中拦截器设计精髓之责任链模式
一、责任链模式讲解
1.1、责任链模式的特点
责任链模式可以使请求能被多个对象依次进行处理,从而使这些对象形成一条完整的链路,这样就会使请求由起始点,顺着这条链路,依次向下一个对象进行传递,直至传递到最后一个对象,责任链模式的应用场景主要是以下几种情况。
第一:当一个请求可能会被某个环节修改,并且该环节修改请求后,会将该请求继续下发给下一个环节。
第二:请求处理环节可能会发生改变,有可能改变其中某个环节,也有可能所有的流程会发生更新,例如软件项目中会发生一些流程规则改变,甚至去掉某个流程。通常,这种需求就需要将请求的处理环节,由外部来决定,从而使高层接口抽象部分,在运行时根据外部提供的具体实现,来实现多态调用。
第三:请求过程中,可能会存在重定向操作、重试机制,这种需求常出现在网络框架中,为了确保网络框架的健壮性,当发生重定向,或进行重试回滚时,就需要将请求中的部分参数,进行更新。
责任链模式的最大特点是,首先做到了对请求的依次向下传递,这样使处理该请求的所有对象,都可以共享到这个请求体。其次是责任链模式中用于处理请求的每个对象,并不需要关心该请求的上一个或下一个处理者,只需要将当前处理的进度,组织成请求处理者遵循的接口规范即可,并且确保请求处理者的响应,也遵循统一的接口规范,这样就能使请求处理链路,在某一时刻一旦满足客户端需求时,能直接打断,并将响应结果反馈给客户端。因此,使用责任链模式时,需要注意以下几个方面。

1.2、责任链模式设计思想
第一:由于一个请求会被多个处理者进行处理,这就需要请求的处理者,接收请求时,需要遵循指定的接口规范。而请求可能也会有多种不同的类型,因此,这就需要对请求进行抽象接口定义,而请求的抽象处理者在定义时,接收请求的方法中对请求的依赖,就需要定义为对请求接口的依赖。这样一来,请求接收者在接收请求时,就实现了接收与请求之间的松耦合。
第二:由于责任链模式中,当前处理者对请求进行处理后,会将响应返回。因此,这就需要所有的请求处理者,返回的响应头,也要遵循统一的接口规范。
第三:请求处理者可能会存在多个,具体处理者并不能确定,而且处理者的执行顺序不同,常发生在流程改变,或某个流程规则发生替换时。因此,这就需要有一个容器,将所有的请求处理者进行保存,并且这个容器需要由客户端传递过来。此外还要确保容器的数据类型,定义为请求处理者接口,只有这样,容器中才可以添加不同类型的请求处理者。
第四:最后还要确保实现链式调用,使容器中记录的请求处理者,根据请求,依次通过不同的请求处理者来处理请求。所以,接下来还需要提供一个专门用于执行请求处理者的执行链。
执行链需要记录客户端指定的所有请求处理者,并记录当前请求处理者的光标索引,同时还需要记录客户端请求。这样一来,当执行链被触发时,会在触发调用的方法中,再次创建一个执行链next,并为这个next定义光标值+1,这就会使next这个执行链被执行时,从处理者容器中获取下一个处理者并将其执行,而这下一个处理者,会再次创建一个执行链,并在此基础上更新光标数值。这样一来,执行链会被多次创建,而每次创建执行时,都会访问获取处理者容器中的下一个元素,并将该元素执行,这样就将容器中的所有处理者,以链的方式一一执行。每个处理者元素,都将会返回响应,由于这时已经确保所有处理者按照在容器中的顺序一一执行,因此,按照顺序将响应进行拼接,这就形成了客户端请求按照顺序,逐一被请求处理者执行,并最终将响应结果拼接,从而就实现了通过责任链模式的链式调用。

1.3、责任链模式拦截器在OKHttp中的使用
在OKHttp框架的RealCall的execute方法中,会提交客户端请求到线程池中,从而实现网络请求。在execute方法中,getResponseWithInterceptorChain方法,就是OKHttp框架中对拦截器的调用部分,这个方法返回了Response响应,而响应就是根据getResponseWithInterceptorChain方法中执行的所有拦截器的返回结果,进行拼接而成的。接下来进入该方法查看一下拦截器是如何实现并调用的。
这里写图片描述
在getResponseWithInterceptorChain方法中,首先创建了一个interceptors容器,然后分别将客户端自定义、请求重试、桥接、缓存、连接等不同职责的拦截器,依次添加到了这个容器中,每个拦截器就相当于责任链上对请求的不同处理者。最后,创建了一个RealInterceptorChain拦截链,并将interceptors容器、当前光标位置和客户端传递过来的请求Request,添加到了这个拦截链中,并通过RealInterceptorChain的proceed方法触发拦截链,使每个拦截器被依次执行,代码如下所示。
在OKHttp网络请求框架中,拦截器Interceptor是该框架的设计精髓,也是对责任链模式的一个经典应用。这里将网络请求中的每个环节的处理者,定义了统一的Interceptor拦截器接口,使每个环节的实现,都是基于Interceptor接口规范来完成的。通过这种设计思想,也使得每个环节更加独立,职责更单一。
这里写图片描述
接下来我们分析OKHttp框架中拦截器链式调用设计是如何实现的。我们根据OKHttp框架的Interceptor、RealInterceptorChain,自己设计一个与该框架类似的拦截器应用,通过模拟缓存访问、连接到服务器、处理服务器返回数据等,来看看如何通过责任链模式,实现拦截器链式调用。

1.4、通过责任链模式实现拦截器
1.4.1、定义拦截器抽象接口层
先来回顾一下前面关于对责任链模式设计思想概括的前两点,前两点中强调了实现链式调用的前提条件,要确保接收的请求和返回的响应,遵循统一的接口规范。只有这样,拦截器与拦截器之间,才能进行请求的向下派发或回滚,以及响应结果的拼接。
链式调用还需要确保每个拦截器接收到拦截链执行器,并在拦截器中调用执行器中的方法,从而使执行器再次创建,并使拦截器光标定位到下一个节点,从而触发下一个节点的拦截器。以此类推,最终形成一整套的链式调用。
因此,定义拦截器抽象层时,需要提供两个接口,代码如下所示。
这里写图片描述
这里写图片描述
Interceptor是对拦截器的抽象接口定义,在intercept方法中,将会接收一个Chain。而Chain就是拦截链的抽象定义,这样一来,当拦截器的具体实现在调用intercept方法时,就可以通过Chain拦截链,调用request方法取出客户端请求,然后再调用process方法从而创建下一个节点的Chain,这时,在下一个节点的Chain中,将会定位到下一个Interceptor拦截器。由此类推,直至最后一个拦截器Interceptor,不再执行Chain的process方法。因为执行到最后一个拦截器时,后面不再有拦截器,所以在最后一个拦截器调用后,就需要将最终的响应结果,反馈给客户端了。

1.4.2、实现拦截链RealInterceptorChain
以上内容对拦截器及拦截链的抽象层进行了分析,接下来我们分析拦截链(Chain接口的具体实现)是如何实现拦截器被依次按顺序从第1个元素开始,执行到最后一个元素的。
回顾责任链模式的特点及设计思想,处理者的数量并不确定,也就是说,某个环节的处理过程,可能会随时发生改变,也可能整个处理流程都会发生变更。因此,对于拦截链的具体实现RealInterceptorChain来说,不能在其内部定义拦截器的具体实现,而是需要接收一个从客户端传入的拦截器集合。只有这样,当RealInterceptorChain被拦截器再次触发创建时,就可以根据当前拦截器集合以及光标索引,获取到下一个拦截器,并将其执行,最终形成链式调用。
以下是RealInterceptorChain的构造方法,创建时通过接收客户端指定的拦截器集合、光标和请求,可以确保RealInterceptorChain在process方法被执行时,能根据客户端请求request,以及光标index和interceptors容器,获取当前这个节点的Interceptor拦截器,并通过对光标index+1的方式,创建用于执行下一个Interceptor拦截器的RealInterceptorChain。
接下来执行当前拦截器的intercept方法,并将下一个节点的RealInterceptorChain传入,就可以确保当前拦截器的具体实现中,可以根据传入的RealInterceptorChain,再次执行process方法,从而依次迭代,使每个拦截器都被执行。
这里写图片描述
在process方法中,每次调用该方法时都会创建RealInterceptorChain的next对象,并根据当前index光标索引,取出当前拦截器,执行intercept方法,并传入next。这样一来就可以在具体的Interceptor拦截器中,来决定是否需要继续调用RealInterceptorChain对象的process方法。如果继续调用此方法,则框架会继续执行下一个节点的拦截器,否则,将会终止,并直接向客户端返回结果。
这里写图片描述

1.4.3、拦截器的具体实现
对于一个复杂的网络请求框架来说,按每个不同的流程进行拆分,可以拆分为缓存、连接、重试或重定向、处理服务器返回数据等多个不同的环节。由于前面的内容中,我们通过对拦截器以及请求、响应的接口规范进行定义,已经可以确保这些流程环节,均遵循统一的接口协议。因此,接下来通过实现不同的拦截器,为后面的代码测试进行准备。
再来回顾一下关于Interceptor拦截器接口的特点和作用,通过面向接口编程,主要带来了以下这些优势。
这里写图片描述
结合前面分析的RealInterceptorChain构造方法以及RealInterceptorChain中的process方法,发现在RealInterceptorChain拦截链中,只有创建下一节点拦截链、获取当前拦截器、执行当前拦截器并返回结果的方法,这里并没有对拦截器的具体实现产生依赖,其拦截器容器中定义的泛型,都是对Interceptor这个抽象接口产生的依赖。
这样的设计就实现了拦截链RealInterceptorChain与客户端调用之间的松耦合,即使某个拦截器发生了改变,或流程顺序发生改变等,RealInterceptorChain都不需要进行任何调整。这就确保了核心的拦截链代码的稳定性和健壮性,无论客户端提供的容器中包含多少个拦截器,RealInterceptorChain都能正常处理,并逐一使这些拦截器被链式调用。
以下是缓存拦截器CacheInterceptor,这个拦截器在执行时模拟了检查缓存操作,为了能看到模拟网络请求的过程,使网络连接、解析服务器响应的拦截器依次被执行,在这个缓存拦截器中,通过RealInterceptorChain的process方法,使下一个拦截器执行。
这里写图片描述
网络连接的拦截器模拟了连接到服务器的操作,接下来通过RealInterceptorChain的process方法,使解析服务器响应数据的拦截器被执行。
这里写图片描述
解析服务器响应数据的拦截器,模拟解析数据后,通过RealInterceptorChain使下一个拦截器执行。
这里写图片描述
ResultCallBackInterceptor为最后一个拦截器,因此,这里直接向客户端返回响应数据,不再通过RealInterceptorChain派发请求了。
这里写图片描述
1.4.4、拦截器在客户端的调用
接下来在测试类中测试拦截器的执行,首先按照先后执行顺序,依次将不同的拦截器,添加到容器中,然后创建拦截链,并传入客户端的请求、以及客户端定义的容器和光标,最后通过调用process方法,触发链式调用。
这里写图片描述
程序执行结果如下图所示,通过观察运行结果就可以看出,RealInterceptorChain根据客户端指定的所有拦截器,按照先后顺序,依次执行,并最终将结果反馈给客户端。
这里写图片描述