preface

Plenty of time is the first productive force. When the source code read more, for many points combing may not be as specific as before, this article mainly lies in the implementation of the main process analysis. Retrofit had not been used in previous projects, so we decided to look at some retroFIT-related implementations to see how it worked. To understand the dynamic proxy applications in Java, wants to use more concise way to realize network request, the Retrofit is probably the best case study, the previous project in writing network request, every time need to write a lot of boilerplate code to construct a network request, code once more, also can increase the probability of error, This increases maintenance costs and reduces development efficiency. The goal of engineering best practices is for developers to focus only on their business logic and not on the concrete implementation of the lower levels, exposing as little business irrelevant content as possible to the upper levels.

Based on using

For Retrofit, we needed to define an interface class that declared the interface to request and how the parameters were passed. Then we could construct a Service class by calling the create method, and calling the corresponding method would help us send network requests. The code was very simple. At the same time, we can realize the isolation of different business interfaces by dividing different business modules into services.

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);
Copy the code

Source code analysis

The creation of a Retrofit instance is mainly about configuring some information. The core analysis here is the create method. The analysis in this article will follow the implementation of the CREATE method to the invocation of its final network request. At the same time, combined with the Retrofit source code analysis to further expand the analysis of dynamic proxy implementation in Java.

public <T> T create(final Class<T> service) {
    validateServiceInterface(service);
    return(T) Proxy.newProxyInstance(service.getClassLoader(), new Class<? >[] { service }, newInvocationHandler() { private final Platform platform = Platform.get(); private final Object[] emptyArgs = new Object[0]; Invoke @override public @nullable Object invoke(Object proxy, Method Method, @Nullable Object[] args) throws Throwable { // If the method is a method from Objectthen defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          returnplatform.invokeDefaultMethod(method, service, proxy, args); } // Call loadServiceMethod for other ways we have definedreturn loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
      }
    });
}
Copy the code

Create was a key method in Retrofit’s implementation and was our entry point for source code analysis. The first method, validateServiceInterface, validates the passed Service class and fetches information about the methods defined by the annotations. The specific core implementation is as follows.

for (Method method : service.getDeclaredMethods()) {
    if(! platform.isDefaultMethod(method) && ! Modifier.isStatic(method.getModifiers())) { loadServiceMethod(method); }}Copy the code

The method defined in the service is iterated over and handled by calling loadServcieMethod

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;
}
Copy the code

The loadServiceMethod method is one of the core points of Retrofit’s implementation of the entire framework. The loadServiceMethod method is searched from the ServiceMethodCache. If it is found, it is returned, and if it is not, it calls the ServiceMethod annotation method. ParseAnnotaion generates a ServiceMethod and adds it to the cache. So let’s take a look at the relevant implementation of ServiceMethod and its annotation parsing method.

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);
}
Copy the code

The core implementation here is in RequestFactory parseAnnoations and HttpServiceMethod’s parseAnnotations method. RequestFactory then simply builds a RequestFactory using Retorift and Method to record the method’s parameters, return value types, and annotation information. Annotations are used to parse the information needed to send a network request from the annotations. When the Invoke method is invoked, the parseAnnotations are used to construct a network request.

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

In a dynamic proxy we actually execute the following code when we make a method call

loadServiceMethod(method).invoke(args ! = null ? args : emptyArgs);Copy the code

In the case of the loadServiceMethod method, which we have examined previously, we construct an OkHttp request by looking in the cache of the method resolution and then calling its invoke method. The corresponding request method of OKHTTP is then invoked.

conclusion

For Rerofit use in the project, the top layer is our business logic, the bottom layer is the API we defined for each business logic module, and the third layer is Retrofit’s bridge from the API layer to the network requesting the OKhttp layer, and ultimately sending the request through OKhttp. It also uses annotations and dynamic proxies to make code implementation more elegant. With the help of the form of interface, the network request is well isolated in the business layer. The combination of annotations and dynamic proxies is also instructive for future development and design.