IK.AM

@making's tech note


TypeSafeで使いやすいJavaのハイレベルRESTクライアントRetrofit

🗃 {Programming/Java/retrofit}
🏷 Java 🏷 Retrofit 
🗓 Updated at 2014-05-05T06:49:28Z  🗓 Created at 2014-05-05T06:49:28Z   🌎 English Page

JavaのRESTクライアントといれば

  • Spring FrameworkのRestClient
  • JAX-RS 2のClient API あたりがメジャーで使いやすい。

ただし、ちょっとboilerplateなコードが増えてしまう。この記事のTodoRestRepositoryとか。

最近見つけたRetrofitがよさげ。Interfaceだけ作れば実装はライブラリが提供してくれる(Proxyを作成してくれる)のが今風な作り。

以下、使用例

pom.xml

<dependency>
    <groupId>com.squareup.retrofit</groupId>
    <artifactId>retrofit</artifactId>
    <version>1.5.0</version>
</dependency>

REST Client

インタフェースのメソッドに対して、対応するHTTPのアノテーションをつけるだけ。

package restclient;

import retrofit.http.GET;
import retrofit.http.Path;
import retrofit.http.Query;


public interface EntryRestRepository {
    @GET("/entries")
    Page<Entry> findAll();

    @GET("/entries")
    Page<Entry> findAll(@Query("page") int page, @Query("size") int size);

    @GET("/entries/{entryId}")
    Entry findOne(@Path("entryId") Long entryId);
}

非同期にも対応している。

入出力に対応するモデルは普通のJavaBeanでOK。

package restclient;

@lombok.Data
public class Entry {
    private Long entryId;
    private String title;
    private String contents;
    private String format;
}

ネストにも対応している。

package restclient;

import java.util.List;

@lombok.Data
public class Page<T> {
    private List<T> content;
    private String sort;
    private long totalPages;
    private long totalElements;
    private boolean firstPage;
    private boolean lastPage;
    private long numberOfElements;
    private int size;
    private int number;
}

使用例

public static void main(String[] args) {
    RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint("http://blog.ik.am/api/v1")
            .build();
    EntryRestRepository repository = restAdapter.create(EntryRestRepository.class);

    System.out.println(repository.findAll());
    System.out.println(repository.findAll(0, 2));
    System.out.println(repository.findOne(259L));
}

非同期処理

返り値をvoidにして引数にretrofit.Callbackを取ればよい。

@GET("/entries")
void findAllAsynnc(@Query("page") int page, @Query("size") int size, Callback<Page> cb);

@GET("/entries/{entryId}")
void findOneAsync(@Path("entryId") Long entryId, Callback<Entry> cb);

使い方は、↓な感じ。

repository.findOneAsync(260L, new Callback<Entry>() {
    @Override
    public void success(Entry entry, Response response) {
        System.out.println(entry);
    }

    @Override
    public void failure(RetrofitError error) {
        error.printStackTrace();
    }
});

JavaFXなどGUIアプリでメインスレッドからHTTPアクセスすると固まちゃう場合にはこっちを使いたい。 FunctionalInterfaceじゃないのでラムダ使えないのが辛い。

JSONコンバーターをJacksonに変える

RetrofitはデフォルトでJSONコンバーターにGsonを使用する。依存ライブラリ的にJacksonの方が都合がいいって場合もあると思うが、RetrofitではJSONコンバーターを入れ替えることができる。

pom.xmlを以下のように書き換える

<dependency>
    <groupId>com.squareup.retrofit</groupId>
    <artifactId>retrofit</artifactId>
    <version>1.5.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>com.squareup.retrofit</groupId>
    <artifactId>converter-jackson</artifactId>
    <version>1.5.0</version>
</dependency>

コンバーターはRestAdapterに設定して差し替える。

RestAdapter restAdapter = new RestAdapter.Builder()
        .setEndpoint("http://blog.ik.am/api/v1")
        .setConverter(new JacksonConverter())
        .build();
EntryRestRepository repository = restAdapter.create(EntryRestRepository.class);

JacksonではおなじみのObjectMapperJacksonConverterに設定可能。

ObjectMapper objectMapper = new ObjectMapper();

RestAdapter restAdapter = new RestAdapter.Builder()
        .setEndpoint("http://blog.ik.am/api/v1")
        .setConverter(new JacksonConverter(objectMapper))
        .build();
EntryRestRepository repository = restAdapter.create(EntryRestRepository.class);

GsonとJacksonの違いは色々あるけど、GsonだとJavaBeanには存在しないがJSONに存在するフィールドがあると無視するけど、Jacksonの方はUnrecognizedPropertyExceptionをスローする。Gson側の挙動に合わせるには、以下のように@JsonIgnoreProperties(ignoreUnknown = true)をつける必要がある。

package restclient;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@lombok.Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class Entry {
    private Long entryId;
    private String title;
    private String contents;
    private String format;
}

クライアント側は使いたいフィールドだけ持てば良いのでこちらの方が良いかもしれない。ミスに気づきにくくなるかもだけど。


これから使っていきたいと思った。

JavaFXと相性が良い気がする。

JAX-RSのClientだとちょっと辛かった

コンバーターはProtocol Bufferにも対応しているみたい。


✒️️ Edit  ⏰ History  🗑 Delete