preface

Chain of responsibility pattern, just as its name implies, is a certain responsibility unit to form a chain, on the chain, each responsibility unit is responsible for all should be responsible for its own responsibility, and responsibility between elements of each other noninterference, when any event to deal with, starting from the chain the first responsibility of the unit, the first responsibility themselves responsible for part of the unit process events, After the event is processed, if the event is still unprocessed and needs to be further processed and the current responsibility unit cannot handle it or is not responsible for it, the current responsibility unit will transfer the event to the next responsibility unit, and the latter responsibility unit does not care about which one to deal with. The current responsibility unit only needs to deal with its part of responsibility and determine whether the event should be passed on to the next responsibility unit.Copy the code

For example,

The responsibility chain model used in Android development

Okhttp or Retrofit network request framework is used in Android development, and okHTTP uses the chain of responsibility design pattern, even if you use Retrofit, and Retrofit is just an okHTTP wrapper. The interceptors in OKHTTP use the chain of responsibility design pattern, which I’m sure you’ve used to handle network requests, such as cookies. Here’s a look at the chain of responsibility design pattern from the implementation source of the OKHTTP interceptor.

1. Use of interceptors

If we need to handle cookies, we will generally define an Interceptor class that inherits from Interceptor and rewrite the Intercept method to handle cookies in this method (adding or retrieving cookie saves). The following code implements an Interceptor that adds cookies to the request header. The method of retrieving the cookie in the request header is similar and not shown here.

/** * defines an OkHttp3 interceptor that handles the headers of packets and adds cookies */ to the headers
public class InterceptorOfAddCookie implements Interceptor {

    private static final String TAG = "InterceptorOfAddCookie";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Log.d(TAG, "InterceptorOfAddCookie");
        returnchain.proceed(chain.request()); }}Copy the code

I then add an interceptor to the okhttpClient object using the following addInterceptor method, which takes the interceptor class object as an argument. Here I add two interceptors, including adding and fetching cookies.

okHttpClient = new OkHttpClient
        .Builder()
        .addInterceptor(new InterceptorOfAddCookie())
        .addInterceptor(new InterceptorOfReceivedCookie())
        .build();
Copy the code

The entry point is the addInterceptor method here. Take a look at the source code of this method to see what kind of logic is implemented internally.

/** * Add a custom cookie-handling interceptor to the addInterceptor method. The interceptors are a List of interceptors. The set of interceptors * here can be regarded as the chain of responsibility in our chain of responsibility pattern, and each interceptor in the set is * equivalent to the unit of responsibility mentioned above. * /
public Builder addInterceptor(Interceptor interceptor) {
  if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
  interceptors.add(interceptor);
  return this;
}
Copy the code

Then you see the process of using the interceptor in ohhttpClient and sending the request

okHttpClient = new OkHttpClient
        .Builder()
        .addInterceptor(new InterceptorOfAddCookie())
        .addInterceptor(new InterceptorOfReceivedCookie())
        .build();

Request request = new Request.Builder().url("").get().build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {}@Override
    public void onResponse(Call call, Response response) throws IOException {}});Copy the code

Interceptors are added to the okhttpClient interceptors collection. Okhttpclient.newcall (request) = okHttpClient.newCall(request) = okHttpClient.newCall(request)

@Override public Call newCall(Request request) {
  return RealCall.newRealCall(this, request, false /* for web socket */);
}
Copy the code

You can see that the newCall method actually creates a RealCall object, and the RealCall class implements the Call method. RealCall enqueue(CallBack); RealCall enqueue(CallBack); RealCall enqueue();

@Override public void enqueue(Callback responseCallback) {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  transmitter.callStart();
  client.dispatcher().enqueue(newAsyncCall(responseCallback)); } you can see that an AsyncCall object is created and a CallBack object is passed in, In the RealCall class, you can find that the qualified inner class AsyncCall inherits from NamedRunnable, and further check that NamedRunnable inherits from Runnable, so AsyncCall can be seen as a RunnableCopy the code

The client. The dispatcher (). The enqueue (new AsyncCall (responseCallback)); The enQueue method in the Dispatcher class,

void enqueue(AsyncCall call) {
  synchronized (this) {
    readyAsyncCalls.add(call);

    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
    // the same host.
    if(! call.get().forWebSocket) { AsyncCall existingCall = findExistingCallWithHost(call.host());if(existingCall ! =null) call.reuseCallsPerHostFrom(existingCall); } } promoteAndExecute(); } You can see that the promoterAndExecute() method ends up being called, and look at the implementation in this methodprivate boolean promoteAndExecute(a) {
  assert(! Thread.holdsLock(this));

  List<AsyncCall> executableCalls = new ArrayList<>();
  boolean isRunning;
  synchronized (this) {
    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall asyncCall = i.next();

      if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
      if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.

      i.remove();
      asyncCall.callsPerHost().incrementAndGet();
      executableCalls.add(asyncCall);
      runningAsyncCalls.add(asyncCall);
    }
    isRunning = runningCallsCount() > 0;
  }

  for (int i = 0, size = executableCalls.size(); i < size; i++) {
    AsyncCall asyncCall = executableCalls.get(i);
    asyncCall.executeOn(executorService());
  }

  return isRunning;
}
Copy the code

You can see that asynccall.executeon (executorService()) is eventually called in this method; The executeOn method is passed as an ExecutorService instance of the thread pool object. Check the implementation of the executeOn method back in AsyncCall.

void executeOn(ExecutorService executorService) {
  assert(! Thread.holdsLock(client.dispatcher()));boolean success = false;
  try {
    executorService.execute(this);
    success = true;
  } catch (RejectedExecutionException e) {
    InterruptedIOException ioException = new InterruptedIOException("executor rejected");
    ioException.initCause(e);
    transmitter.noMoreExchanges(ioException);
    responseCallback.onFailure(RealCall.this, ioException);
  } finally {
    if(! success) { client.dispatcher().finished(this); // This call is no longer running!Executorservice.execute (executorService.execute(executorService.execute))this); Is tothis(AsyCall object, while AsyncCall mentioned that its parent NameRunnable implements Runnable) is passed to the thread pool, and when the thread pool executes the Runnable task, the run() method is called. The NameRunnable class of AsyncCall overrides the execute() method by calling its own run() method. Execute () of NameRunnable executes the execute() method of AsyncCall class.Copy the code

To see the execute () method, in this method is mainly to see the Response the Response = getResponseWithInterceptorChain (); This line, check the getResponseWithInterceptorChain () method of implementation:

Response getResponseWithInterceptorChain(a) throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(new RetryAndFollowUpInterceptor(client));
  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, transmitter, null.0,
      originalRequest, this, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  boolean calledNoMoreExchanges = false;
  try {
    Response response = chain.proceed(originalRequest);
    if (transmitter.isCanceled()) {
      closeQuietly(response);
      throw new IOException("Canceled");
    }
    return response;
  } catch (IOException e) {
    calledNoMoreExchanges = true;
    throw transmitter.noMoreExchanges(e);
  } finally {
    if(! calledNoMoreExchanges) { transmitter.noMoreExchanges(null); Interceptors () = interceptors();}} interceptors() = interceptors(); The client interceptor collection contains only our own custom interceptor collection. When creating an instance of okhttpClient, add a custom interceptor method. So it can also be found that if the interceptor processing will execute our custom interceptor before executing the internal interceptor. Further down, you'll find interceptor.chain Chain =newThe RealInterceptorChain() is passed to iterceptors to create the interceptor.chain, which is called the responsibility Chain. Note: RealInterceptorChain implements the internal interface of the Interceptor interface, the Chain interface. Response Response = chain.proceed(originalRequest); The proceed method of the chain is called and the Request object is passed to originalRequest.Copy the code

Proceed to the implementation of the chain.proceed method (in the RealInterceptorChain class, which implements the chain interface, so the logical implementation is in the class’s proceed) :

@Override public Response proceed(Request request) throws IOException {
  returnproceed(request, transmitter, exchange); } the proceed method is still called internally.public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
    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.exchange ! =null&&!this.exchange.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.exchange ! =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, transmitter, exchange,
      index + 1, request, call, 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(exchange ! =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;
}

// The most important lines of code are these three lines
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,index + 1, request, call, connectTimeout, readTimeout, writeTimeout); Interceptor interceptor = interceptors.get(index); Response response = interceptor.intercept(next); This method is constructed by calling the RealInterceptorChain class, but the difference here is that the parameter index is index+1And index is a global variable in this class, so the value of index is incremented by1The index of interceptors is interceptor, and the Chain method of interceptor is implemented. Then we will review the logic of our original custom interceptor.public class InterceptorOfAddCookie implements Interceptor {

    private static final String TAG = "InterceptorOfAddCookie";

    @Override
    public Response intercept(Chain chain) throws IOException {
        Log.d(TAG, "InterceptorOfAddCookie");
        returnchain.proceed(chain.request()); }} The chain.proceed(Request) method is called in the Intercept (Chain) method. The RealInterceptorChain rewrites its parent's method, which is called the "proceed" method. The proceed method also calls the interceptor.Intercept () method, so a recursion is formed here, and the recursion is the core idea of the chain of responsibility mode. The intercept(Chain) method in the interceptor is constantly implemented, and we only need to implement our logic in the Intercept method. We can obtain Request or Response through the Chain and process the Request body or Request header. For example, cookies in request headers.Copy the code
conclusion

The interceptor implementation in OKHTTP can be summarized as follows:

Responsibility unit (extracted as interface for easy expansion)public interface Interceptor {
  Response intercept(Chain chain) throws IOException; } chain of responsibilitypublic final class Chain {

  private final List<Interceptor> interceptors;
  
  public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
        @Nullable Exchange exchange, int index, Request request, Call call,
        int connectTimeout, int readTimeout, int writeTimeout) {
      this.interceptors = interceptors;
      this.index = index;
      this.request = request; . }@Override public Response proceed(Request) throws IOException {
    returnProceed (Request, Transmitter, Exchange); }public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
      throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    
    RealInterceptorChain next = new RealInterceptorChain(interceptors,transmitter, exchange,
    index + 1, request, call, connectTimeout, readTimeout,   writeTimeout);
    // Note the index+1 here
    Interceptor interceptor = interceptors.get(index);
    interceptor.intercept(Chain);// The Chain is created through the constructor
    returnresponse; }} Responsibility unit logic code implementationpublic class InterceptorOfAddCookie implements Interceptor {

    private static final String TAG = "InterceptorOfAddCookie";

    @Override
    public Response intercept(Chain chain) throws IOException {
        /** * this side implements the specific logical processing */
        return chain.proceed(Request);
        // Call the proceed method in the chain of responsibilities for recursive invocation
        // The request object is obtained by defining the fetch method in the Chain}}Copy the code

Such a design approach is obviously easy to extend later and does not involve expecting logical changes to the unit of responsibility. Simply create a class that implements the unit of responsibility interface, create an instance of that class, and add it to the chain of responsibility. The key idea of this design mode lies in recursion. By recursively calling the method of responsibility unit in the responsibility Chain, the event to be processed can be transferred along the responsibility Chain for processing, and the event can be logically judged to be continued to the next responsibility unit in the responsibility unit.