概要
少し前に、Retrofit + Gsonを使ってWeb API(はてなブックマークエントリー情報取得API)を実行する方法に関する記事を作成しました。
インタフェースを作成するだけでWeb APIアクセス処理が可能とする実装方法について、githubからRetrofitのソースコードを取得して調べました。
その結果、reflection APIのProxyを使っていることがわかりました。
処理が冗長に/見通しが悪くなるため、アプリの実装でProxyを使うケースはあまりないかもしれませんが、面白い使い方だと思いました。
Retrofitとは
Retrofitは、Android(Java)アプリで、Web API(jsonデータ)アクセスを簡単に実現するためのオープンソースのライブラリです。
jsonデータ変換ライブラリ(gson, jacksonなど)を組み合わせて利用します。
RetrofitによるWeb API実行処理
Web API実行処理は、以下の手順で実装します。
API用インタフェースを実装
まず、API用インタフェースを実装します。
以下のインタフェースは、はてなブックマークエントリー情報取得APIの例です。
// 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://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などに実装します。
- Retrofitインスタンス作成
- Web APIアクセス用インタフェース作成
- Web API実行
// 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呼び出しを実行します。結果は、引数のコールバックで取得出来ます。
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の使い方は、こちらを参考にさせていただきました。
$ 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();
}
}