Retrofitの内部処理について調べてみました


# 概要
少し前に、Retrofit + Gsonを使ってWeb API(はてなブックマークエントリー情報取得API)を実行する方法に関する[記事](http://qiita.com/unokun/items/b8b7697f94e0907ad8a0)を作成しました。

インタフェースを作成するだけでWeb APIアクセス処理が可能とする実装方法について、[github](https://github.com/square/retrofit)からRetrofitのソースコードを取得して調べました。

その結果、reflection APIのProxyを使っていることがわかりました。

処理が冗長に／見通しが悪くなるため、アプリの実装でProxyを使うケースはあまりないかもしれませんが、面白い使い方だと思いました。

# Retrofitとは
Retrofitは、Android(Java)アプリで、Web API(jsonデータ)アクセスを簡単に実現するためのオープンソースのライブラリです。
jsonデータ変換ライブラリ(gson, jacksonなど)を組み合わせて利用します。

# RetrofitによるWeb API実行処理

Web API実行処理は、以下の手順で実装します。

## API用インタフェースを実装
まず、API用インタフェースを実装します。
以下のインタフェースは、はてなブックマークエントリー情報取得APIの例です。

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

    // はてなブックマークエントリー情報取得API
    // http://developer.hatena.ne.jp/ja/documents/bookmark/apis/getinfo
    @GET("/entry/jsonlite/")
    Call<BookmarkEntry> getBookmarkEntry(@Query("url") String target);

}
```

以下のURLは、テクノロジーに関するはてなブックマークエントリー情報取得API(GET)のURLです。
ここで、urlはカテゴリを示す引数です。
はてなブックマークエントリー情報取得APIの仕様は、[ここ](http://developer.hatena.ne.jp/ja/documents/bookmark/apis/getinfo)を参照してください。

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

インタフェースにはこのURLを生成できる情報を記述します。

### エンドポイント
エンドポイントは、Web APIのルートURLです。
Retrofitのインスタンスを生成するときに使います。

### メソッド
メソッド名は任意です。
アノテーション(@GET)は、GETメソッドを使うこと、Web APIの相対パスが```/entry/jsonlite/```であることを示しています。また、アノテーション(@Query)はクエリストリングとして?以下に追加されます。

## Web APIアクセス処理の実装
Web APIアクセスは以下の手順で行います。
(Main)Activityなどに実装します。

1. Retrofitインスタンス作成
2. Web APIアクセス用インタフェース作成
3. Web API実行


```java
 	// Retrofitインスタンス作成
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(HatenaApiInterface.END_POINT)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    // Web APIアクセス用インタフェース作成
    mApiInterface = retrofit.create(HatenaApiInterface.class);
    
    // Web API実行用インスタンス(インタフェース)を取得します。
    Call<BookmarkEntry> call = mApiInterface.getBookmarkEntry(targetUrl);
    
    // Web APIを実行します
    // コールバック呼び出しで処理結果が通知されます 
    call.enqueue(new Callback<BookmarkEntry>() {
        @Override
        public void onResponse(Call<BookmarkEntry> call, Response<BookmarkEntry> response) {
            // Web APIアクセスが成功した場合の処理を記述します
            }

        @Override
        public void onFailure(Call<BookmarkEntry> call, Throwable t) {
            // Web APIアクセスが失敗した場合の処理を記述します
        }
    });
```

# 内部処理の解説
以下は、Retrofit.createのソースです。

reflection APIのProxyを使って、InvocationHandlerを作成して返しています。
取得したインタフェース(インスタンス)のgetBookmarkEntryを実行すると、invokeメソッドが実行されます。引数のMethodには、getBookmarkEntryというメソッド名、引数argsには、String targetUrlが格納されます。

このメソッドには関係づけられているWeb APIアクセス情報を、loadServiceMethodで取得します。さらにokHttp呼び出しクラスを追加して、Adapterクラスを返します。これを、```Call<BookmarkEntry> call```型の変数にセットします

つぎに、enquequeメソッドを実行するとWeb API呼び出しを実行します。結果は、引数のコールバックで取得出来ます。

```java
  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);
          }
        });
  }
```

# Proxyの使い方(サンプル)
reflection APIのProxyを使い方を説明します。
百聞は一実行にしかず。
TestInterfaceとTestProxyを作成し、TestProxyを実行してみてください。
上記の説明がよくわかると思います。

Proxyの使い方は、[こちら](http://qiita.com/obaQ/items/e401d41059d81149da52)を参考にさせていただきました。

```java
$ 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 {
					    // メソッド情報を出力する
						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();
	}
}
```

# 参考
* [square/retrofit: Type\-safe HTTP client for Android and Java by Square, Inc\.](https://github.com/square/retrofit)
* [はてなブックマークエントリー情報取得API \- Hatena Developer Center](http://developer.hatena.ne.jp/ja/documents/bookmark/apis/getinfo)
* [java\.lang\.reflect\.Proxyの使い方（１） \- Qiita](http://qiita.com/obaQ/items/e401d41059d81149da52)