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ではおなじみのObjectMapper
もJacksonConverter
に設定可能。
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にも対応しているみたい。