[JAVA] I investigated the internal processing of Retrofit

Overview

Not long ago, I created an article on how to execute Web API (Hatena Bookmark Entry Information Acquisition API) using Retrofit + Gson.

I got the source code of Retrofit from github and investigated the implementation method that enables Web API access processing just by creating an interface.

As a result, I found that I am using Proxy of reflection API.

Since the processing is verbose / poor visibility, there may not be many cases where Proxy is used in the implementation of the application, but I thought it was an interesting usage.

What is Retrofit

Retrofit is an Android (Java) app, an open source library for easy Web API (json data) access. Use in combination with json data conversion library (gson, jackson, etc.).

Web API execution process by Retrofit

Web API execution process is implemented by the following procedure.

Implement interface for API

First, implement the interface for API. The following interface is an example of Hatena Bookmark Entry Information Acquisition API.

// HatenaApiInterface.java
public interface HatenaApiInterface {
    String END_POINT = "http://b.hatena.ne.jp";
    String TARGET_URL = "http://b.hatena.ne.jp/ctop/it";

    //Hatena Bookmark Entry Information Acquisition API
    // http://developer.hatena.ne.jp/ja/documents/bookmark/apis/getinfo
    @GET("/entry/jsonlite/")
    Call<BookmarkEntry> getBookmarkEntry(@Query("url") String target);

}

The following URL is the URL of the Hatena Bookmark Entry Information Acquisition API (GET) related to technology. Where url is a category argument. Please refer to here for the specifications of Hatena Bookmark Entry Information Acquisition API.

http://b.hatena.ne.jp/entry/jsonlite/?url=http%3A%2F%2Fb.hatena.ne.jp%2Fctop%2Fit

Describe the information that can generate this URL in the interface.

end point

The endpoint is the root URL of the Web API. Used when creating an instance of Retrofit.

Method

The method name is arbitrary. The annotation (@GET) indicates that you are using the GET method and that the relative path of the Web API is `/ entry / jsonlite /`. Also, the annotation (@Query) is added as a query string under?

Implementation of Web API access processing

Follow the steps below to access the Web API. (Main) Implement in Activity etc.

  1. Create a Retrofit instance
  2. Creating an interface for Web API access
  3. Web API execution
 	//Retrofit instance creation
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(HatenaApiInterface.END_POINT)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    //Creating an interface for Web API access
    mApiInterface = retrofit.create(HatenaApiInterface.class);
    
    //Web API execution instance(interface)To get.
    Call<BookmarkEntry> call = mApiInterface.getBookmarkEntry(targetUrl);
    
    //Run the web API
    //The processing result is notified by the callback call
    call.enqueue(new Callback<BookmarkEntry>() {
        @Override
        public void onResponse(Call<BookmarkEntry> call, Response<BookmarkEntry> response) {
            //Describes the process when Web API access is successful
            }

        @Override
        public void onFailure(Call<BookmarkEntry> call, Throwable t) {
            //Describes the process when Web API access fails
        }
    });

Explanation of internal processing

Below is the source for Retrofit.create.

InvocationHandler is created and returned using Proxy of reflection API. When you execute getBookmarkEntry of the acquired interface (instance), the invoke method is executed. The method name getBookmarkEntry is stored in the argument Method, and the String targetUrl is stored in the argument args.

Get the Web API access information associated with this method with loadServiceMethod. Add another okHttp call class and return the Adapter class. Set this to a variable of type Call <BookmarkEntry> call

Then execute the enqueque method to make a Web API call. The result can be obtained by calling back the argument.

  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 the method is a method from Object then defer to normal invocation.
            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);
          }
        });
  }

How to use Proxy (sample)

Explains how to use Proxy of reflection API. Seeing is believing. Create TestInterface and TestProxy and try running TestProxy. You can see the above explanation well.

For how to use Proxy, I referred to here.

$ java TestProxy
$ cat TestInterface.java
public interface TestInterface {
	public void doSomething();
}

$ cat TestProxy.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TestProxy {
	private Object proxy;
	private TestProxy(Class<TestInterface> clazz) {
		this.proxy = Proxy.newProxyInstance(clazz.getClassLoader(),
				new Class[] { clazz },
				new InvocationHandler() {
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
					    //Output method information
						System.out.println("method: " + method);
						return null;
					}
		});
	}
	
	public static TestInterface createProxy(Class<TestInterface> clazz) {
		TestProxy obj = new TestProxy(clazz);
		return clazz.cast(obj.proxy);
	}
	
	public static void main(String[] args) {
		TestInterface someInterface = TestProxy.createProxy(TestInterface.class);
		someInterface.doSomething();
	}
}

reference

Recommended Posts

I investigated the internal processing of Retrofit
I investigated the enclosing instance.
I read the source of ArrayList I read
I read the source of Integer
I read the source of Long
I read the source of Short
I read the source of Byte
Order of processing in the program
I read the source of String
I investigated the mechanism of attr_accessor (* Hoge :: ATTRIBUTES) that I sometimes see
I want to understand the flow of Spring processing request parameters
[day: 5] I summarized the basics of Java
[Spring Boot] I investigated how to implement post-processing of the received request.
I checked the specification of cols attribute of AsciiDoc table & internal implementation of Asciidoctor
[Swift] I investigated the variable expansion "\ ()" that realizes the mysterious function of Swift UI.
Back calculation of the transition of the internal seed of Random
I want to output the day of the week
I checked the place of concern of java.net.URL # getPath
I understood the very basics of character input
I compared the characteristics of Java and .NET
I want to var_dump the contents of the intent
I touched on the new features of Java 15
I tried using the profiler of IntelliJ IDEA
I checked the number of taxis with Ruby
Try the free version of Progate [Java I]
I examined the life cycle of the extension of JUnit Jupiter
I tried using the Server Push function of Servlet 4.0
I was addicted to the record of the associated model
I tried to summarize the state transition of docker
I saw the list view of Android development collectively
[Each, map ...] I compared the array processing tonight [ruby]
05. I tried to stub the source of Spring Boot
I tried to reduce the capacity of Spring Boot
I tried the new feature profiler of IntelliJ IDEA 2019.2.
I want to know the answer of the rock-paper-scissors app
Monitor the internal state of Java programs with Kubernetes
Image processing: The basic structure of the image read by the program
I want to display the name of the poster of the comment
I summarized the display format of the JSON response of Rails
I wrote a sequence diagram of the j.u.c.Flow sample
I summarized the types and basics of Java exceptions
[WIP] I tried the configuration of Docker + Streama + NFS
I am keenly aware of the convenience of graphql-code-generator, part 2
I can't get out of the Rails dbconsole screen
I investigated the super that I met with the devise controller
I learned about the existence of a gemspec file
[Rails] I investigated the difference between redirect_to and render.
Control the processing flow of Spring Batch with JavaConfig.
I want to be aware of the contents of variables!
I want to return the scroll position of UITableView!
The world of clara-rules (2)
I checked the specification of how to specify cell attributes of AsciiDoc table & internal implementation of Asciidoctor
Judgment of the calendar
The world of clara-rules (4)
The world of clara-rules (1)
The world of clara-rules (3)
I investigated test automation
The world of clara-rules (5)
The idea of quicksort
The idea of jQuery
I made the server side of an online card game ①