一直对于责任链模式具体如何实现没有一个很好的实践,最近刚好用 OKHttp mock 的时候发现它是用 Interceptor 实现的,而 Interceptor 的调用上看起来也是责任链模式的,可以钻进去看看。
OKHttp 可以为 client 设置 interceptor
client = new OkHttpClient().newBuilder()
.addInterceptor(mockInterceptor)
.retryOnConnectionFailure(true).build();
Interceptor 的接口定义如下:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
Interceptor 接口本身只有一个 intercept 方法,它接受一个 Chain 对象。而 Chain 则是一个内部接口。
在使用 client 进行 http 请求时,OKHttpClient 先生成一个 RealCall 对象,请求发生在 RealCall 的 execute 方法里面。
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
上面对状态的修改和 listenter 的通知先不管, 看看 InterceptorChain 的调用 getResponseWithInterceptorChain。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
初始化了一个 RealInterceptorChain,初始化的 index 是0。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
可以看到 RealInterceptorChain 的逻辑就是调用当前 index 的 inteceptor 的 intercept 方法。偷巧的地方在于,它构造了一个新的 RealInterceptorChain,将 index+1,交由当前的 Interceptor 决定是否要继续调用 chain 上面的 interceptor。Chain 本身不关心对于 chain 上面的所有 intercetpor 的调用或者返回值判断(null 除外),都交给 Interceptor 实现。
比如 Mock 的实现上,就是自定义了一套 Behavior,当 Behavior 是 relayed 时,并且当前没有任何匹配规则,MockInterceptor 就会将请求继续在 Chain 上传递。
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
for (Iterator<Rule> it = rules.iterator(); it.hasNext(); ) {
Rule rule = it.next();
Response response = rule.accept(request);
if (rule.isConsumed()) {
it.remove();
}
if (response != null) {
return response;
} else if (behavior == Behavior.SEQUENTIAL) {
StringBuilder sb = new StringBuilder("Not matched next rule: ");
sb.append(rule);
sb.append(", request=");
sb.append(request);
sb.append("\nFailed to match:");
int i = 0;
for (Map.Entry<Matcher, String> e : rule.getFailReason(request).entrySet()) {
sb.append("\n\t");
sb.append(++i);
sb.append(": ");
sb.append(e.getValue());
sb.append("; matcher=");
sb.append(e.getKey());
}
throw new AssertionError(sb.toString());
}
}
// no matched rules or no more rules
if (behavior == Behavior.RELAYED) {
return chain.proceed(request);
}
StringBuilder sb = new StringBuilder("Not matched any rule: request=");
sb.append(request);
if (rules.isEmpty()) {
sb.append("\nNo remaining rules!");
} else {
sb.append("\nRemaining rules:");
int i = 0;
for (Rule rule : rules) {
sb.append("\n\t");
sb.append(++i);
sb.append(": ");
sb.append(rule);
}
}
throw new AssertionError(sb.toString());
}
OKHttp 还把真正的 Http 请求也做成了多个 Interceptor,也就是 RealCall 在构建 RealInterceptorChain 的时候会加进去的,包括:
- RetryAndFollowUpInterceptor。它排在第一个,因为它要控制对于 chain 的重试。
- BridgeInterceptor。负责将 UserRequest 转化为 NetworkRequest,添加各种 header。然后把 NetworkResponse 转化为 UserResponse,分析其中的 header 和 body 类型。
- CacheInterceptor。这个就是本地 cache 了。可以缓存一些请求和返回值。
- ConnectionInterceptor。创建连通目标服务器的 connection。
- CallServerInterceptor。真正的 http 请求。
需要注意的是,自定义的 Interceptor 是排在这几个 Interceptor 前面的,这也给了自定义 Interceptor 去修改 request 和 response 的机会,只需要通过调用 chain.proceed(),就可以获取后续的 Interceptor 的返回值。
设计上的一些亮点:
- 通过传递链条来让各组件自己控制是否继续。
- 链条的调用带返回值,使得上游组件能够覆盖或者修改下游组件的入参和返回。
- 链条的执行是自发的,由外部控制的,不是类似 manager 的统一控制模式,从而避免了复杂的返回值判断和传递。
- 扩展性上,自定义的放在前面,固定的放在后面,这个算是比较常见的 default 实现了。不过还是那句话, chain 带返回值和控制移交给 Interceptor 是个很不错的设计,使得可交互性大大增强。