Retrofitを試す

Retrofitを試してみたメモです。


Javaのhttp clientはずっとapache commonsのHttpClientを使用していたのですが、
Retrofitが便利らしいので試してみました。

下記のバージョンで試してみます。

  • java8
  • retrofit 2.4.0
  • okhttp 3.10.0
  • jackson-databind 2.9.5

Retrofit

Retrofitはインターフェースにアノテーションでリクエストを定義して使用するJava, Android用のhttp clientです。
apache commons HttpClientよりもリクエスト/レスポンスの処理が感覚的で簡潔に書けます。
http clientの実装としてokhttpを使用してます。

Retrofit

Dependency

gradleプロジェクトで試してみます。
dependencyには下記を追加しました。

build.gradle

dependencies {
    compile 'com.squareup.retrofit2:retrofit:2.4.0'
    compile 'com.squareup.retrofit2:converter-jackson:2.4.0'
    compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5'
    compile 'com.squareup.okhttp3:logging-interceptor:3.10.0'
    compileOnly 'org.projectlombok:lombok:1.16.22'
    testCompile 'junit:junit:4.12'
    testCompile 'org.assertj:assertj-core:3.10.0'
}

Retrofitではconverterをプラグインとして適用できます。
今回はjson用にconverter-jacksonを追加しました。
(Gsonなど他のconverterもあります)
https://github.com/square/retrofit/wiki/Converters


テスト用の簡易サーバをGoで立ち上げて試してみます。

 

API Interface

RetrofitではAPIを定義したインターフェースを作成します。
MyServiceというインターフェースを定義します。

MyService.java

public interface MyService {
    @GET("dog")
    Call<Dog> getDog();

    @GET("dogs")
    Call<List<Dog>> getDogList();

    @GET("dog?aaa=bbb")
    Call<Dog> getDogWithParam();

    @GET("dog")
    Call<Dog> getDogWithParam(@Query("aaa") String value);

    @GET("dog")
    Call<Dog> getDogWithParams(@QueryMap Map<String, String> params);

    @Headers({
            "User-Agent: my-service:0.1",
            "my-header: zzz"
    })
    @GET("dog")
    Call<Dog> getDogWithHeaders();

    @GET("dog")
    Call<Dog> getDogWithDynamicHeader(@Header("User-Agent") String userAgent);

    @GET("dogs/{id}")
    Call<Dog> getDogById(@Path("id") int id);

    @POST("dogs")
    Call<Void> create(@Body Dog dog);

    @FormUrlEncoded
    @POST("dogs/form")
    Call<Void> form(@Field("id") int id,
                    @Field("name") String name);

    @PUT("dogs/{id}")
    Call<Void> update(@Path("id") int id,
                      @Body Dog dog);

    @DELETE("dogs/{id}")
    Call<Void> delete(@Path("id") int id);
}

インターフェースで使用するmodelとしてDogクラスを定義します。

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Dog {
    private int id;
    private String name;
}

Retrofit Client

Retrofit.Builderクラスを使用してRetrofitインスタンスを生成します。
baseUrl()でサーバのURLを指定し、JacksonConverterFactoryを指定してRetrofitインスタンスを生成します。
create()でAPIを定義したインターフェースを指定します。

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:8080/")
        .addConverterFactory(JacksonConverterFactory.create())
        .build();

MyService myService = retrofit.create(MyService.class);

Retrofitではhttp clientの実装としてokhttpを使用するのですが、
自分でokhttpのインスタンスを指定することが出来ます。
logging interceptorを追加したokhttpを設定してみます。
(okhttpのinterceptorについてはこちらで記事を書いてます)

下記をdependencyに追加。

build.gradle

    compile 'com.squareup.okhttp3:logging-interceptor:3.10.0'

Retrofitのインスタンス生成を下記のように修正します。

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(new HttpLoggingInterceptor().setLevel(Level.BODY))
        .build();

Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://localhost:8080/")
        .client(okHttpClient)
        .addConverterFactory(JacksonConverterFactory.create())
        .build();

myService = retrofit.create(MyService.class);

 

GET

単純にGETしてみます。
@GETアノテーションでエンドポイントを指定します。
CallにレスポンスのDogクラスを指定します。

MyService.java

@GET("dog")
Call<Dog> getDog();

テスト。
Call.execute()でhttp requestが同期実行されます。
Response.body()でDogへデシリアライズされて取得する事が出来ます。

MainTest.java

@Test
public void getDogTest() throws Exception {
    Response<Dog> response = myService.getDog().execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dog

 

GET(List)

GETのレスポンスをListで取得してみます
Call<List<Dog>>でListとして指定するだけです。

MyService.java

@GET("dogs")
Call<List<Dog>> getDogList();

MainTest.java

@Test
public void getDogList() throws Exception {
    Response<List<Dog>> response = myService.getDogList().execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    List<Dog> dogs = response.body();
    System.out.println("dogs: " + dogs);
}

クライントログ

dogs: [Dog(id=1, name=pochi), Dog(id=2, name=john)]

サーバ側ログ

[method] GET
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dogs

 

GET(request parameter)

固定のrequest parameterを指定する場合はURLとしてそのまま記述出来ます。

MyService.java

@GET("dog?aaa=bbb")
Call<Dog> getDogWithParam();

MainTest.java

@Test
public void getDogWithParamTest() throws Exception {
    Response<Dog> response = myService.getDogWithParam().execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[header] Connection: Keep-Alive
[path] /dog
[param] aaa: bbb

 

GET(@Query)

@Queryでrequest parameterのkey, valueを指定できます。

MyService.java

@GET("dog")
Call<Dog> getDogWithParam(@Query("aaa") String value);

MainTest.java

@Test
public void getDogWithParamQueryTest() throws Exception {
    Response<Dog> response = myService.getDogWithParam("ccc").execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dog
[param] aaa: ccc

 

GET(@QueryMap)

@QueryMapで複数のrequest parameterをまとめて指定できます。
Mapで指定します。

MyService.java

@GET("dog")
Call<Dog> getDogWithParams(@QueryMap Map<String, String> params);

MainTest.java

@Test
public void getDogWithParamsTest() throws Exception {
    Map<String, String> params = new HashMap<>();
    params.put("aaa", "bbb");
    params.put("size", "100");
    Response<Dog> response = myService.getDogWithParams(params).execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dog
[param] aaa: bbb
[param] size: 100

 

GET(@Headers)

@Headersで固定http headerを指定できます。
okhttpのinterceptorでも固定http headerを指定することも出来ます。
(例:OkHttp3を試す - abcdefg.....)

MyService.java

@Headers({
        "User-Agent: my-service:0.1",
        "my-header: zzz"
})
@GET("dog")
Call<Dog> getDogWithHeaders();

MainTest.java

@Test
public void getDogWithHeadersTest() throws Exception {
    Response<Dog> response = myService.getDogWithHeaders().execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] User-Agent: my-service:0.1
[header] My-Header: zzz
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[path] /dog

 

GET(@Header)

@Headerでhttp headerを指定出来ます。

MyService.java

@GET("dog")
Call<Dog> getDogWithDynamicHeader(@Header("User-Agent") String userAgent);

MainTest.java

@Test
public void getDogWithDynamicHeaderTest() throws Exception {
    Response<Dog> response = myService.getDogWithDynamicHeader("my-header:0.2").execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] User-Agent: my-header:0.2
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[path] /dog

 

GET(@Path)

@Pathでrequest pathを指定出来ます。
@GETのエンドポイントのpathに{name}の形式で指定し、@Path("name")で値を指定します。

MyService.java

@GET("dogs/{id}")
Call<Dog> getDogById(@Path("id") int id);

MainTest.java

@Test
public void getDogByIdTest() throws Exception {
    Response<Dog> response = myService.getDogById(1).execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
    Dog dog = response.body();
    System.out.println("dog: " + dog);
}

クライントログ

dog: Dog(id=1, name=pochi)

サーバ側ログ

[method] GET
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dogs/1

 

POST(json)

jsonでPOSTしてみます。
@POST()でエンドポイントを指定して、@Bodyでrequest bodyを指定します。
レスポンスは特に取得しないのでVoidを指定しています。

MyService.java

@POST("dogs")
Call<Void> create(@Body Dog dog);

MainTest.java

@Test
public void createTest() throws Exception {
    Dog dog = new Dog(100, "pudding");
    Response<Void> response = myService.create(dog).execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
}

サーバ側ログ

[method] POST
[header] Content-Type: application/json; charset=UTF-8
[header] Content-Length: 27
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dogs
[request body row] {"id":100,"name":"pudding"}
[request body decoded] {ID:100 Name:pudding}

 

POST(form)

formでPOSTしてみます。
form形式で送信するために@FormUrlEncodedを指定し、@Fieldでformのkeyとvalueを指定します。

MyService.java

@FormUrlEncoded
@POST("dogs/form")
Call<Void> form(@Field("id") int id,
                @Field("name") String name);

MainTest.java

@Test
public void formTest() throws Exception {
    Response<Void> response = myService.form(200, "太郎").execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
}

サーバ側ログ

[method] POST
[header] Content-Type: application/x-www-form-urlencoded
[header] Content-Length: 30
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[path] /dogs/form
[request body row] id=200&name=%E5%A4%AA%E9%83%8E
[request body decoded]  id=200&name=太郎

 

PUT

PUTは@PUTでエンドポイントを指定します。

MyService.java

@PUT("dogs/{id}")
Call<Void> update(@Path("id") int id,
                  @Body Dog dog);

MainTest.java

@Test
public void updateTest() throws Exception {
    Dog dog = new Dog(300, "santa");
    Response<Void> response = myService.update(2, dog).execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
}

サーバ側ログ

[method] PUT
[header] Content-Length: 25
[header] Connection: Keep-Alive
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[header] Content-Type: application/json; charset=UTF-8
[path] /dogs/2
[request body row] {"id":300,"name":"santa"}
[request body decoded] {ID:300 Name:santa}

 

DELETE

DELETEは@DELETEでエンドポイントを指定します。

MyService.java

@DELETE("dogs/{id}")
Call<Void> delete(@Path("id") int id);

MainTest.java

@Test
public void deleteTest() throws Exception {
    Response<Void> response = myService.delete(3).execute();
    if (!response.isSuccessful()) {
        String url = response.raw().request().url().toString();
        System.out.println("error!!" + response.errorBody() + " url=" + url);
    }
}

サーバ側ログ

[method] DELETE
[header] Accept-Encoding: gzip
[header] User-Agent: okhttp/3.10.0
[header] Connection: Keep-Alive
[path] /dogs/3


ソースコードは下記にあげました。
github.com


おわり。