[learning notes of RPC framework I] deeply explain the concept and use of Retrofit2 framework

Posted by edkellett on Sat, 22 Jan 2022 20:33:37 +0100

Some time ago, because Dubbo's interface was used, I learned the RPC call method of Zookeeper+Dubbo and summarized a Blog [SpringBoot learning notes 14] integrated development of SpringBoot+Dubbo+Zookeeper Recently, because there is a contract related development task that uses Retrofit2 extensively, which was also a half familiar imitation before, I still hope to have a systematic study and understand the usage and basic principle of Retrofit2 as a whole. Because Retrofit2 is encapsulated based on OkHttp, I will introduce OkHttp in a systematic study in the near future, Or sort it out according to this context:

  • What is it: what is Retrofit2 and what is the basic concept
  • Why: why do you choose to use Retrofit2? In what scenario is Retrofit2 better
  • How to use: basic usage, how to use
  • Underlying principle: what underlying principle and source code support the function of Retrofit2

Follow this line for a deep understanding.

Retrofit2 basic concepts

First question, what is it? Retrofit2 is a popular network request framework. It can be understood as an enhanced version of okhttp. Okhttp is encapsulated at the bottom. To be exact, retrofit2 is an encapsulation of Restful Http network request framework. Because the network request is essentially completed by okhttp, and retrofit is responsible for the encapsulation of the network request interface. The overall process is shown in the figure below:

In essence, the application requests the network through Retrofit. In essence, it uses the Retrofit interface layer to encapsulate the request parameters, Header, Url and other information, and then OkHttp completes the subsequent request work. After the server returns the data, OkHttp gives the original data to Retrofit, which parses it according to the user's needs.

Retrofit2 framework advantages

Second question, why? Now that you have OkHttp, why should you encapsulate a layer of Retrofit on it? That goes back to the essence of the framework. What is the role of the framework? Let users work less and focus only on the core business logic, then the role of retrofit is obvious. It is easy to use. It allows users to make web calls foolishly. In addition, there are the following points:

  • Super decoupling, interface definition, interface parameters and interface callback are no longer coupled together, and Retrofit is completely encapsulated
  • Different httpclients can be configured to implement network requests, such as OkHttp and httpclient
  • Support synchronous, asynchronous and Rxjava. Best practice: Retrofit + OkHttp + RxJava
  • Different deserialization tool classes can be configured to parse different data, such as json and xml
  • Fast request speed, easy to use, flexible and concise

These advantages may not be well understood, so compare the usage of OkHttp and Retrofit:

OkHttp framework usage

The template step code is as follows:

private void testOkHttp() throws IOException {
    //Step 1: create an HttpClient object, that is, build an instance of a network type. Generally, all network requests will use the same singleton object
    final OkHttpClient client = new OkHttpClient();
    
    //Step 2: build a Request, that is, build a specific network Request object, specific Request url, Request header and Request body
     RequestBody formBody = new FormBody.Builder()
             .add("size", "10")
             .build();
    final Request request = new Request.Builder()
     .url("https://www.baidu.com")
     .post(formBody)
     .build();
     
    //Step 3: build request Call, that is, bind the specific network request with the entity executing the request to form a specific formal executable entity
    Call call = client.newCall(request);
  
    //step4 sends a network request, obtains data and carries out subsequent processing
    call.enqueue(new Callback() {
      @Override
      public void onFailure(Call call, IOException e) {
     }
      @Override
     public void onResponse(Call call, Response response) throws IOException {
          Log.i(TAG,response.toString());
          Log.i(TAG,response.body().string());
     }
   });
 }

OkHttp is a set of request clients encapsulated based on Http protocol. Although it can also open threads, it basically prefers real requests, and its responsibilities are the same as those of HttpClient. OkHttp is mainly responsible for socket optimization, such as multiplexing, buffer caching and data compression, but there are still some problems to be solved:

  1. The interface configuration of user network request is cumbersome, especially when the request body, request header and parameters need to be configured
  2. The data analysis process requires the user to manually get the response body for analysis, which cannot be reused;
  3. Unable to adapt automatic thread switching

Let's look at the implementation of Retrofit:

Retrofit framework usage

The template step code is as follows:

//step1: create a retrofit object and build a carrier object of network request, which is the same as okhttp building OkhttpClient object. However, retrofit has a lot of initialization contents during building, which can provide preparation for subsequent network requests, such as ready-made conversion Executor, Gson convert and RxJavaCallAdapter
Retrofit retrofit = new Retrofit.Builder()
       .baseUrl("https://www.baidu.com/")
       .addConverterFactory(GsonConverterFactory.create(new Gson()))
       .build();

//Step 2: the essence of retrofit is to uniformly configure network requests, complete the setting of dynamic agents, and the parameters are reflected in the interface through annotations
ISharedListService sharedListService =  retrofit.create(ISharedListService.class);

//step3: build a specific network Request object Request
//1) Translate the annotations in the interface into corresponding parameters;
//2) Determining the return value response type of the network request interface and the corresponding converter;
//3) Encapsulate Okhttp's Request as OKhttpCall of Retrofit
Call<SharedListBean> sharedListCall = sharedListService.getSharedList();

//step4: make a network request and process the data requested by the network
sharedListCall.enqueue(new Callback<SharedListBean>() {
  @Override
  public void onResponse(Call<SharedListBean> call, Response<SharedListBean> response{
    if (response.isSuccessful()) {
        System.out.println(response.body().toString());
      }
    }
    @Override
    public void onFailure(Call<SharedListBean> call, Throwable t) {
     t.printStackTrace();
   }
});

The comparison between the two in the overall process is as follows:

Retrofit2 usage practice

The third question, how to use it? After we have a general understanding of why we want to use the Retrofit2 framework, we need to realize its benefits in practice.

Retrofit2 common notes

Retrofit uses a large number of annotations to simplify requests. Retrofit abstracts okhttp requests into java interfaces and uses annotations to configure and describe network request parameters. Next, you can analyze according to this example:

   /**
     * @Description: Seal interface
     * @Param: [headers, application, userId, contractSealManualParameter]
     * @Author: tianmaolin
     * @Date: 2021/12/17
     */
    @POST("/tml/contract/contractSealManual")
    Call<CommonContractResponse<String>> contractSealManual(@HeaderMap Map<String, String> headers,
                                                            @Query("application") String application,  @Query("userId") Long userId,
                                                            @Body ContractSealManualParameter contractSealManualParameter);

It can be roughly divided into the following categories:

1 request method annotation

The annotation added to the requested method, such as @ POST("/tml/contract/contractSealManual") in the above example. Other uses include:

2 request header annotation

The contents placed in the header in the request, such as @ headermap map < string, string > headers in the above example, and others:

3 request Parameter annotation

Request parameters to be prevented in the parameters, such as @ Query("userId") Long userId or @ Body ContractSealManualParameter contractSealManualParameter in the above example. Other parameters are:

Note: the parameters of each method in the interface should be marked with annotations, otherwise an error will be reported.

4 request and response format (tag) annotation

These annotations are generally used to handle requests with special data transmission:

Retrofit2 usage steps

Through the above understanding, it is not difficult to find that Retrofit abstracts the okhttp request into a java interface, uses annotations to describe and configure network request parameters, uses dynamic agents to "translate" the annotations of the interface into an Http request, and finally executes the Http request. After we have a general understanding of common annotations, let's use the framework according to the steps in the above template code.

1. Define the general Retrofit processing method

All Retrofit based agents can be implemented to extract the commonalities of some requests, such as setting the request read timeout, write timeout, adding response data Convert, etc.

public class BaseRetrofitProxy<T> {
    @Resource
    private OkHttpClient okHttpClient;
    
    @PostConstruct
    public void initClient() {
        okHttpClient = new OkHttpClient.Builder().connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
            .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS).writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
            .addInterceptor(new OkHttpSignInterceptor()
            .build();
    }

    public T getRetrofit(String baseUrl, Class<T> proxy) {
        Retrofit retrofit = new Retrofit.Builder().client(okHttpClient).baseUrl(baseUrl)
            .addConverterFactory(JacksonConverterFactory.create(JsonUtils.getObjectMapper())).build();
        return retrofit.create(proxy);
    }
}

2 define proxy request interface and interface method

We need to create a retro fit object to build a network request carrier object. Usually, we use a retro fit. We take the request to send SMS interface as an example, which runs through all the following steps:

/**
 * * @Name ContractRetrofitProxy
 * * @Description SMS / email RPC calling interface
 * * @author tianmaolin
 * * @Data 2021/11/18
 */
@RetrofitClient(baseUrl = "${retrofit.message.host}")
public interface MessageRetrofitProxy {

    /**
     * @Description: Send SMS:
     * @Param: [sendMessageParam]
     * @Author: tianmaolin
     * @Date: 2021/11/24
     */
    @POST("/message/send")
    Call<CommonMessageResponse<SendMessageResponse>> sendMessage(@Body SendMessageParam sendMessageParam);
}

The meanings of the above parts are as follows:

  • @RetrofitClient(baseUrl = "${retrofit.message.host}"): the domain name written in the configuration file for the request
  • @POST("/message/send"): the relative path of the method
  • Call < commonmessageresponse < sendmessageresponse > >: the generic type in call is the type that receives the return value
  • @Body SendMessageParam sendMessageParam: requested parameters

Such a network request interface defined by annotations is completed. Of course, the input parameter and return value here need to be defined in advance

3 Create proxy object and send request

In this step, we need to use the above defined interface to create proxy objects and call network interface methods:

/**
 * * @Name MessageRetrofitProxyCall
 * * @Description
 * * @author tianmaolin
 * * @Data 2021/11/24
 */
@Slf4j
@Service
public class MessageRetrofitProxyCall extends BaseRetrofitProxy<MessageRetrofitProxy> {

    @Resource
    private MessageRetrofitProxy messageRetrofitProxy;
    @Value("${retrofit.message.host}")
    private String domain;

    //step1: create a retrofit object and set the dynamic proxy
    @PostConstruct
    public void init() {
        messageRetrofitProxy = getRetrofit(domain, MessageRetrofitProxy.class);
    }

    /**
     * @Description:Remotely call the SMS sending interface to return the SMS sending result
     * @Param: [sendMessageParam]
     * @return: com.ke.merchant.kernel.rpc.retrofit.message.model.response.SendMessageResponse
     * @Author: tianmaolin
     * @Date: 2021/11/24
     */
    public SendMessageResponse remoteSendMessage(SendMessageParam sendMessageParam) {
        try {
            //Step 2: build a specific network request object
            Call<CommonMessageResponse<SendMessageResponse>> result = messageRetrofitProxy.sendMessage(sendMessageParam);
            LOGGER.info("send message request,sendMessageParam:{}", sendMessageParam);
            //step3: execute the network request and get the response result
            CommonMessageResponse<SendMessageResponse> response = execute(result);
            LOGGER.info("send message response,sendMessageParam:{}", response);

            if (response != null && ZERO == response.getErrno()) {
                LOGGER.info("send message success in message platform,{}", sendMessageParam.toString());
                return response.getData();
            }
        } catch (Exception e) {
            LOGGER.error("remote send message error ,sendMessageParam:{}", sendMessageParam, e);
        }
        return null;
    }

    /**
     * @Description: RPC Call execution method
     * @Param: [call]
     * @return: T
     * @Author: tianmaolin
     * @Date: 2021/11/24
     */
    @Override
    protected <T> T execute(Call<T> call) {
        try {
            Response<T> response = call.execute();
            return response.body();
        } catch (IOException e) {
            throw new ServiceException("MessageRetrofitProxyCall request fail", [Add link description](https://tianmaolin.blog.csdn.net/article/details/119702083)e).code("1096").tip("MessageRetrofitProxyCall request fail");
        }
    }
}

After reading the complete example, let's go back and review the benefits of Retrofit:

  • Super decoupling: the interface definition, interface parameters and interface callback are no longer coupled together, and Retrofit is encapsulated: the above interface definition and parameters are implemented by MessageRetrofitProxy, and the execution and callback are implemented by MessageRetrofitProxyCall. The two are in decoupling status respectively.
  • Different httpclients can be configured to implement network requests, such as OkHttp and httpclient: we can configure different clients in BaseRetrofitProxy
  • Support synchronous, asynchronous and Rxjava. Best practice: Retrofit + OkHttp + RxJava. If execute is used during the request, it is a synchronous implementation, and if enqueue is used, it is an asynchronous implementation
  • Different deserialization tool classes can be configured to parse different data, such as json and xml. In BaseRetrofitProxy, we can configure different parsers.
  • Fast request speed, easy to use, flexible and concise. It is convenient and flexible. It feels very thorough. It makes network requests like adjusting interfaces. No experiments have been done in terms of performance

In general, the calling experience is very good, and all request data are configurable.

Retrofit2 source code analysis

The last link is the underlying principle. What underlying principle and source code support the function of Retrofit2? Let's find out in this part. First, let's look at how retrofit creates proxy objects for interfaces. This is actually the core of retrofit, that is, creating dynamic proxy objects. The above content is divided into two steps:

public T getRetrofit(String baseUrl, Class<T> proxy) {
        Retrofit retrofit = new Retrofit.Builder().client(okHttpClient).baseUrl(baseUrl)
            .addConverterFactory(JacksonConverterFactory.create(JsonUtils.getObjectMapper())).build();
        return retrofit.create(proxy);
    }

1. Build Retrofit object

The general source code is as follows, which is equivalent to configuring some infrastructure for Retrofit:

public static final class Builder {
    private final Platform platform;
    private @Nullable okhttp3.Call.Factory callFactory;
    private @Nullable HttpUrl baseUrl;
    private final List<Converter.Factory> converterFactories = new ArrayList<>();
    private final List<CallAdapter.Factory> callAdapterFactories = new ArrayList<>();
    private @Nullable Executor callbackExecutor;
    private boolean validateEagerly;

    Builder(Platform platform) {
      this.platform = platform;
    }

    public Builder() {
      this(Platform.get());
    }
    
    /**
     * The HTTP client used for requests.
     *
     * <p>This is a convenience method for calling {@link #callFactory}.
     */
    public Builder client(OkHttpClient client) {
      return callFactory(Objects.requireNonNull(client, "client == null"));
    }

    /**
     * Specify a custom call factory for creating {@link Call} instances.
     *
     * <p>Note: Calling {@link #client} automatically sets this value.
     */
    public Builder callFactory(okhttp3.Call.Factory factory) {
      this.callFactory = Objects.requireNonNull(factory, "factory == null");
      return this;
    }
    /**
     * Set the API base URL.
     *
     * @see #baseUrl(HttpUrl)
     */
    public Builder baseUrl(String baseUrl) {
      Objects.requireNonNull(baseUrl, "baseUrl == null");
      return baseUrl(HttpUrl.get(baseUrl));
    }
    
    /** Add converter factory for serialization and deserialization of objects. */
    public Builder addConverterFactory(Converter.Factory factory) {
      converterFactories.add(Objects.requireNonNull(factory, "factory == null"));
      return this;
    }

    /**
     * Add a call adapter factory for supporting service method return types other than {@link
     * Call}.
     */
    public Builder addCallAdapterFactory(CallAdapter.Factory factory) {
      callAdapterFactories.add(Objects.requireNonNull(factory, "factory == null"));
      return this;
    }

    /**
     * The executor on which {@link Callback} methods are invoked when returning {@link Call} from
     * your service method.
     *
     * <p>Note: {@code executor} is not used for {@linkplain #addCallAdapterFactory custom method
     * return types}.
     */
    public Builder callbackExecutor(Executor executor) {
      this.callbackExecutor = Objects.requireNonNull(executor, "executor == null");
      return this;
    }

    /** Returns a modifiable list of call adapter factories. */
    public List<CallAdapter.Factory> callAdapterFactories() {
      return this.callAdapterFactories;
    }

    /** Returns a modifiable list of converter factories. */
    public List<Converter.Factory> converterFactories() {
      return this.converterFactories;
    }

    /**
     * When calling {@link #create} on the resulting {@link Retrofit} instance, eagerly validate the
     * configuration of all methods in the supplied interface.
     */
    public Builder validateEagerly(boolean validateEagerly) {
      this.validateEagerly = validateEagerly;
      return this;
    }
}
  • baseUrl must be specified. Of course, set the domain name of the access address
  • If callFactory is not set, new OkHttpClient() will be used by default. If you need to set OkHttpClient in detail, you need to build an OkHttpClient object and pass it in
  • callbackExecutor is used to transfer callbacks to UI threads. Of course, it is cleverly designed here. It uses platform objects to judge the platform. Class Forname (""). If it is an Android platform, it will customize an Executor object and use looper Getmainlooper () instantiates a handler object through handler. In the Executor post(runnable)
  • adapterFactories are mainly used to convert calls. Basically, we don't need to customize them ourselves
  • converterFactories are used to convert data, such as converting the returned responseBody into an object; Of course, not only for the returned data, but also for the conversion of general annotated parameters, such as the object identified by @ Body

These are several infrastructure parameters.

2 create proxy object

What is a dynamic proxy? In fact, I have learned about the AOP mechanism of MyBaits and Spring before. I won't repeat it here. Post these two blogs here: [MyBatis learning notes iv] source code analysis of the basic operating principle of MyBatis , and [Spring Learning Notes 6] static / dynamic proxy implementation mechanism . To put it bluntly, the dynamic agent is the interceptor of interface methods, adding additional logic during method execution.

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return (T)
        Proxy.newProxyInstance(
            service.getClassLoader(),
            new Class<?>[] {service},
            new InvocationHandler() {
              private final Platform platform = Platform.get();
              private final Object[] emptyArgs = new Object[0];

              @Override
              public @Nullable Object invoke(Object proxy, Method method, @Nullable 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);
                }
                args = args != null ? args : emptyArgs;
                return platform.isDefaultMethod(method)
                    ? platform.invokeDefaultMethod(method, service, proxy, args)
                    : loadServiceMethod(method).invoke(args);
              }
            });
  }

1 loadServiceMethod method

The loadServiceMethod method is used to build a ServiceMethod object:

 ServiceMethod<?> loadServiceMethod(Method method) {
    ServiceMethod<?> result = serviceMethodCache.get(method);
    if (result != null) return result;

    synchronized (serviceMethodCache) {
      result = serviceMethodCache.get(method);
      if (result == null) {
        result = ServiceMethod.parseAnnotations(this, method);
        serviceMethodCache.put(method, result);
      }
    }
    return result;
  }

Here, the parseAnnotations method is called for annotation parsing

 static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    Type returnType = method.getGenericReturnType();
    if (Utils.hasUnresolvableType(returnType)) {
      throw methodError(
          method,
          "Method return type must not include a type variable or wildcard: %s",
          returnType);
    }
    if (returnType == void.class) {
      throw methodError(method, "Service methods cannot return void.");
    }

    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

We see that httpservicemethod is called here Parseannotations, in which the annotation is parsed in the whole process. The overall code is as follows:

static <ResponseT, ReturnT> HttpServiceMethod<ResponseT, ReturnT> parseAnnotations(
      Retrofit retrofit, Method method, RequestFactory requestFactory) {
    boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;
    boolean continuationWantsResponse = false;
    boolean continuationBodyNullable = false;

    Annotation[] annotations = method.getAnnotations();
    Type adapterType;
    if (isKotlinSuspendFunction) {
      Type[] parameterTypes = method.getGenericParameterTypes();
      Type responseType =
          Utils.getParameterLowerBound(
              0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);
      if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {
        // Unwrap the actual body type from Response<T>.
        responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);
        continuationWantsResponse = true;
      } else {
        // TODO figure out if type is nullable or not
        // Metadata metadata = method.getDeclaringClass().getAnnotation(Metadata.class)
        // Find the entry for method
        // Determine if return type is nullable or not
      }

      adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);
      annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);
    } else {
      adapterType = method.getGenericReturnType();
    }

    CallAdapter<ResponseT, ReturnT> callAdapter =
        createCallAdapter(retrofit, method, adapterType, annotations);
    Type responseType = callAdapter.responseType();
    if (responseType == okhttp3.Response.class) {
      throw methodError(
          method,
          "'"
              + getRawType(responseType).getName()
              + "' is not a valid response body type. Did you mean ResponseBody?");
    }
    if (responseType == Response.class) {
      throw methodError(method, "Response must include generic type (e.g., Response<String>)");
    }
    // TODO support Unit for Kotlin?
    if (requestFactory.httpMethod.equals("HEAD") && !Void.class.equals(responseType)) {
      throw methodError(method, "HEAD method must use Void as response type.");
    }

    Converter<ResponseBody, ResponseT> responseConverter =
        createResponseConverter(retrofit, method, responseType);

    okhttp3.Call.Factory callFactory = retrofit.callFactory;
    if (!isKotlinSuspendFunction) {
      return new CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);
    } else if (continuationWantsResponse) {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForResponse<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter);
    } else {
      //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.
      return (HttpServiceMethod<ResponseT, ReturnT>)
          new SuspendForBody<>(
              requestFactory,
              callFactory,
              responseConverter,
              (CallAdapter<ResponseT, Call<ResponseT>>) callAdapter,
              continuationBodyNullable);
    }
  }

The above whole part is the construction of servicemethod object, and the call of servicemethod is as follows:

2. The invoke method gets the call object

 @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

Next, look down at what the adapt method does:

3 the decorator pattern obtains the specific interface method proxy object

final class DefaultCallAdapterFactory extends CallAdapter.Factory {
  private final @Nullable Executor callbackExecutor;

  DefaultCallAdapterFactory(@Nullable Executor callbackExecutor) {
    this.callbackExecutor = callbackExecutor;
  }

  @Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
      throw new IllegalArgumentException(
          "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    }
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    final Executor executor =
        Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
            ? null
            : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
      @Override
      public Type responseType() {
        return responseType;
      }

      @Override
      public Call<Object> adapt(Call<Object> call) {
        return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

You can see that we have determined that the return of the adapt method is: executorcalladapterfactory The corresponding code of get() is:

 static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate;

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
      Objects.requireNonNull(callback, "callback == null");

      delegate.enqueue(
          new Callback<T>() {
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
              callbackExecutor.execute(
                  () -> {
                    if (delegate.isCanceled()) {
                      // Emulate OkHttp's behavior of throwing/delivering an IOException on
                      // cancellation.
                      callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                    } else {
                      callback.onResponse(ExecutorCallbackCall.this, response);
                    }
                  });
            }

            @Override
            public void onFailure(Call<T> call, final Throwable t) {
              callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
            }
          });
    }

    @Override
    public boolean isExecuted() {
      return delegate.isExecuted();
    }

    @Override
    public Response<T> execute() throws IOException {
      return delegate.execute();
    }

    @Override
    public void cancel() {
      delegate.cancel();
    }

    @Override
    public boolean isCanceled() {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override
    public Call<T> clone() {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override
    public Request request() {
      return delegate.request();
    }

    @Override
    public Timeout timeout() {
      return delegate.timeout();
    }
  }
}

It can be seen that ExecutorCallbackCall just encapsulates the Call object, which is similar to the decorator mode, except that the callback during its execution is callback to the UI thread through the callbackExecutor

3 method to execute Call

We have obtained the encapsulated call object of ExecutorCallbackCall type, which is actually the call object we get when writing code. Then we generally execute the execute method to see how the source code does it

@Override
  public Response<T> execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = getRawCall();
    }

    if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
  }

It can be seen that the execution level still depends okhttp3 on.

From the above, we can see that we use proxy When calling any method of the interface, the proxy class generated by newproxyinstance will Call the InvocationHandler#invoke method, in which you can get the passed in parameters, annotations, etc. retrofit can also get all the parameters and annotation information in the invoke method in the same way. Then you can construct the RequestBody, construct the Request, get the Call object, and then return.

To sum up

Retrofit2 is actually a very good framework for optimizing experience. Its core is to call the OkHttp framework for network interface requests. Its biggest advantage is that it is as convenient for us to call the network interface as it is for ordinary interfaces, and make network requests through various annotations. Through the study of source code, we know that it is actually realized through dynamic agent. Dynamic agent also helps us deal with MyBatis framework and AOP. It can be seen that the benefit of knowing a technical principle is much greater than knowing the use of a simple framework, and a technical principle comes from a technical idea, which can be deduced, Know a technical idea > know a technical principle > know the use of a framework.

Topics: Java rpc Network Protocol