Retrofitの仕組みを知ろう

はじめに

Javaアドベントカレンダー5日目担当の null です。
Javaのライブラリやフレームワークで登場するアノテーションは、メソッドやフィールドにつけるだけで値が勝手に入ってきたりして魔法のように思うかもしれません。今回はHTTP ClientのライブラリであるRetrofitを題材にアノテーションがどのように使われているか調べてみましょう。

Retrofitのざっくりした動き

ドキュメントのサンプルからコードを引用します。
ライブラリの利用者はGitHubAPIを叩くインターフェースを用意します。

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

Retrofit#createにインターフェースのClassクラスを渡すとインスタンスが取れます(!)

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

GitHubService service = retrofit.create(GitHubService.class);

以下のようにGitHubServiceのメソッドを呼び出すとなんとAPIを叩いて結果を返してくれます。

Call<List<Repo>> repos = service.listRepos("octocat");

実装を用意していないのにインスタンスが作れてしまったり実装ができていたりして不思議ですね!

魔法の正体を追う

Retrofit#createの実装を覗いてみましょう

github.com

    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
        new InvocationHandler() {
          private final Platform platform = Platform.get();
          private final Object[] emptyArgs = new Object[0];

          @Override public Object invoke(Object proxy, Method method, @Nullable 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);
            }
            return loadServiceMethod(method).invoke(args != null ? args : emptyArgs);
          }
        });

Proxy.newInstanceが怪しそうですね!Java SEのクラスなのでProxyクラスのJavadocを読んでみましょう。
Proxy (Java SE 11 & JDK 11 )

Proxyクラスはプロキシクラスというインターフェースのインスタンスのように振る舞うインスタンスを作るメソッドを提供しています。第二引数のインターフェースを実装したプロキシクラスを作り、プロキシクラスのメソッドが呼び出されると第三引数のInvocationHandlerのinvokeメソッドが呼び出されます。

InvocationHandler#invokeにはプロキシクラスのインスタンス、呼び出されたMethod、メソッドに渡された引数が与えられるので、リフレクションを駆使しつつ処理を行い結果をreturnすればRetrofitの完成という仕掛けでした。

その他のアノテーションが使われる場面

アノテーションは今回のようなリフレクションで使われるだけでなく、アノテーションプロセッシングというコンパイル時にコードを生成する仕組みでも利用されます。
代表的なライブラリだとLombokDomaなどが有名だと思います。アノテーションプロセッシングはリフレクションと違ってコンパイル時に処理するためエラーをより早いタイミングで警告することができるメリットがあります。

おわりに

Retrofitを通じて魔法のようなアノテーションの裏側の仕組みの理解が進むと嬉しいです。
明日は@hishidamaさんの「Asakusa Frameworkと次世代データ処理基盤技術」です。
いつも灰色のページお世話になってます!ありがとうございます!