Retrofit core source code analysis (1) - annotation analysis and dynamic proxy

Retrofit is one of the more popular network request frameworks on the Android platform. It provides a concise and flexible way to handle HTTP requests and responses. Retrofit is designed to make the code for network requests easier to write and read, and it also provides many useful features, such as annotation parsing, dynamic proxy, etc. In this article, we will analyze Retrofit's annotation parsing and dynamic proxy in detail.

Annotation analysis

When using Retrofit, we usually define an interface, which is used to describe the API interface we want to request. In this interface, we can use annotations to describe various aspects of the API, such as HTTP method, request URL, request parameters, etc. Retrofit will generate corresponding network request codes based on these annotations. Here is an example:

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

In this example, the @GET annotation indicates that this is an HTTP GET request, "users/{user}/repos" indicates the URL of the request, and @Path("user") indicates the parameters in the request URL. Retrofit will parse these annotations and generate corresponding network request codes.

Annotation parsing in Retrofit is implemented through the retrofit2.Retrofit#create method in Retrofit.Builder. This method returns a proxy object that parses the annotation and generates a corresponding network request when the interface method is called.

The following is the core code of the retrofit2.Retrofit#create method:

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

                @Override
                public Object invoke(Object proxy, Method method, Object[] args)
                        throws Throwable {
                    if (method.getDeclaringClass() == Object.class) {
                        return method.invoke(this, args);
                    }
                    if (platform.isDefaultMethod(method)) {
                        return platform.invokeDefaultMethod(method, service, proxy, args);
                    }
                    ServiceMethod<Object, Object> serviceMethod =
                            (ServiceMethod<Object, Object>) loadServiceMethod(method);
                    OkHttpCall<Object> okHttpCall = new OkHttpCall<>(serviceMethod, args);
                    return serviceMethod.callAdapter.adapt(okHttpCall);
                }
            });
}

This method first verifies that the interface meets the requirements, and then returns a proxy object. This proxy object implements all methods in the interface, and parses annotations and generates corresponding network requests when calling methods.

We can see that the realization of the proxy object is realized through the java.lang.reflect.Proxy class. The Proxy.newProxyInstance method returns a proxy object that implements all the methods in the specified interface. When we call the method of the proxy object, the proxy object will call the InvocationHandler.invoke method, which implements annotation parsing and network request generation.

In the InvocationHandler.invoke method, it will first judge whether the method of the Object class is called, and if so, directly return the execution result of the method. If not, further judge whether the default method of the interface is called, and if so, use the Platform class to call the default method. Otherwise, call the loadServiceMethod method to parse the annotation and generate a network request.

The loadServiceMethod method will first obtain the ServiceMethod object from the cache, if not in the cache, create a new ServiceMethod object. The ServiceMethod object contains information about network requests, such as HTTP methods, request URLs, and request parameters. The creation of the ServiceMethod object is realized through the ServiceMethod.Builder class, which will parse the annotations on the interface method and generate corresponding network requests.

The following is the core code of the ServiceMethod.Builder class:

public ServiceMethod build() {
    callAdapter = createCallAdapter();
    responseType = callAdapter.responseType();
    if (responseType == Response.class || responseType == okhttp3.Response.class) {
        throw methodError("'"
                + Utils.getRawType(responseType).getName()
                + "' is not a valid response body type. Did you mean ResponseBody?");
    }
    responseConverter = createResponseConverter();
    RequestFactory requestFactory = createRequestFactory();
    return new ServiceMethod<>(requestFactory, callAdapter, responseConverter);
}

In the ServiceMethod.Builder class, a CallAdapter object is first created, which is used to process the result of the network request. Then it will check whether the responseType is a Response or okhttp3.Response type, and if so, throw an exception. Next, a ResponseConverter object is created that converts the result of the network request into a Java object. Finally, a RequestFactory object is created, which is used to create okhttp3.Request objects.

The ServiceMethod object contains information about network requests, including RequestFactory objects, CallAdapter objects, and ResponseConverter objects. The OkHttpCall object is responsible for executing the network request and passing the result to the CallAdapter object for processing. The CallAdapter object finally converts the result into a Java object and returns it to the caller.

dynamic proxy

In the previous code, we have seen the use of dynamic proxies. In Retrofit, we use dynamic proxies for annotation parsing and network request generation. A dynamic proxy is a mechanism by which we can create a proxy object at runtime that performs method calls in place of the original object.

In Retrofit, we use dynamic proxies to create a proxy object that implements an interface. When we call the method of the proxy object, the proxy object will call the InvocationHandler.invoke method, which implements annotation parsing and network request generation. Therefore, we can encapsulate the code of the network request in the interface, making our code more concise and easy to read.

Here's a simple example using a dynamic proxy:

import java.lang.reflect.*

interface HelloWorld {
    fun sayHello()
}

class HelloWorldImpl : HelloWorld {
    override fun sayHello() {
        println("Hello, world!")
    }
}

fun main() {
    val proxy = Proxy.newProxyInstance(
        DynamicProxyExample::class.java.classLoader,
        arrayOf(HelloWorld::class.java),
        object : InvocationHandler {
            private val target: HelloWorld = HelloWorldImpl()
            override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
                println("Before method execution...")
                val result = method?.invoke(target, *(args ?: emptyArray()))
                println("After method execution...")
                return result
            }
        }
    ) as HelloWorld
    proxy.sayHello()
}

In this example, we define an HelloWorldinterface and an HelloWorldImplimplementing class. We then created a proxy object using a dynamic proxy, which implements the HelloWorldinterface. In InvocationHandlerthe invokemethod, we first output a line of log, then call the method HelloWorldImplof the object sayHello, and finally output a line of log. When we call the method of the proxy object sayHello, the proxy object will call InvocationHandler.invokethe method, thus realizing the function of outputting logs before and after method execution. Dynamic proxy is a very powerful mechanism that can be used to implement many functions, such as performance analysis, logging, transaction management, etc. In Retrofit, we use dynamic proxies to implement annotation parsing and network request generation, which makes our code more concise and easier to read.

Guess you like

Origin blog.csdn.net/zl_china/article/details/129393090