daisuzz.log

Spring FrameworkのRestClientを使ってみる

Spring FrameworkのRestClientに慣れていないので、勉強として使ってみる。

前提

RestClientとは

RestClient はSpring Framework6.1から提供されている、HTTPリクエストを実行するためのクライアント。

RestClient (Spring Framework 6.2.12 API)

以前はRestTemplateを使うことが一般的だったが、Spring Framework 5.1からメンテナンスモードになっているため、今は同期的なリクエスト処理であればRestClient, 非同期的なリクエスト処理であればWebClientを使う形が良い。

RestTemplateとは異なりfluentAPIを採用しているため、WebClientと同様メソッドチェーンを使って処理を簡潔に書くことができる。

使い方

RestClientインスタンスの生成

RestClient用のBuilderが提供されているので、それを使ってインスタンスを生成する。

// デフォルトのRestClient
val defaultClient = RestClient.create()

// カスタマイズしたRestClient
val customClient = RestClient.builder()
    .requestFactory(HttpComponentsClientHttpRequestFactory()) // HTTPライブラリを指定
    .messageConverters { converters -> converters.add(MyCustomMessageConverter()) } // MessageConverter を追加
    .baseUrl("https://example.com") // ベースURL
    .defaultUriVariables(mapOf("variable" to "foo")) // デフォルトのURI変数
    .defaultHeader("My-Header", "Foo") // デフォルトのヘッダ
    .defaultCookie("My-Cookie", "Bar") // デフォルトのCookie
    .requestInterceptor(MyCustomInterceptor()) // RequestInterceptor を登録
    .requestInitializer(MyCustomInitializer()) // RequestInitializer を登録
    .build()

GETリクエスト(文字列で受け取る)

RestClientはfluentAPIが提供されているためメソッドをつなげて処理を書くことができる。

val result: String? = defaultClient.get() // GETリクエストの指定
    .uri("https://example.com") // URIの指定
    .retrieve() // レスポンスの取得
    .body(String::class.java) // レスポンスボディをString型に変換

GETリクエスト(オブジェクトで受け取る)

data class Pet(var id: Int = 0, var name: String = "")

val id = 123
val pet: Pet? = defaultClient.get()
    .uri("https://petclinic.example.com/pets/{id}", id) // URI変数をセット
    .accept(MediaType.APPLICATION_JSON) // Acceptヘッダをapplication/jsonに設定
    .retrieve()
    .body(Pet::class.java) // JSONレスポンスをPetオブジェクトに変換

POSTリクエスト(リクエストボディなし)

val responseEntity: ResponseEntity<String> = defaultClient.post()
    .uri("https://example.com")
    .retrieve()
    .toEntity(String::class.java) // レスポンス全体をResponseEntityとして取得

val statusCode: HttpStatusCode = responseEntity.statusCode
val headers: HttpHeaders = responseEntity.headers
val body: String? = responseEntity.body

POSTリクエスト(リクエストボディあり)

// 送信する Pet オブジェクト (例)
val newPet = Pet(name = "Buddy")

val postResponse: ResponseEntity<Void> = defaultClient.post() // POSTリクエストの指定
    .uri("https://petclinic.example.com/pets/new")
    .contentType(MediaType.APPLICATION_JSON) // Content-Typeヘッダをapplication/jsonに設定
    .body(newPet) // リクエストボディを設定
    .retrieve()
    .toBodilessEntity() // レスポンスボディなしの ResponseEntity を取得

エラーハンドリング

RestClientで4xx/5xxが返されるとデフォルトではRestClientExceptionという例外がthrowされる。

try {
    val errorResult: String? = defaultClient.get()
        .uri("https://example.com/this-url-does-not-exist")
        .retrieve()
        .body(String::class.java)
} catch (e: RestClientException) {
    System.err.println("エラーが発生しました: ${e.message}")
}

onStatus()というAPIで特定のエラーを対象に処理を書くことができる。

// 特定のステータスコードに対するハンドリング
val resultWithStatusHandler: String? = defaultClient.get()
    .uri("https://example.com/this-url-does-not-exist")
    .retrieve()
    .onStatus({ statusCode -> statusCode.is4xxClientError }, { request, response ->
        throw RuntimeException("クライアントエラーが発生しました: ${response.statusCode}")
    })
    .body(String::class.java)

リクエスト, レスポンスを使った柔軟な処理

exchange()というAPIを使うことで、リクエストとレスポンスを使ってより自由に処理を書くことができる。

val result: Pet? = defaultClient.get()
    .uri("https://petclinic.example.com/pets/{id}", id)
    .accept(MediaType.APPLICATION_JSON)
    .exchange { request, response -> 
        if (response.statusCode.is4xxClientError) { // 4xx クライアントエラーのチェック
            throw RuntimeException("クライアントエラーが発生しました: ${response.statusCode}")
        } else {
            // レスポンスボディを Pet オブジェクトに変換
            response.bodyTo(Pet::class.java)
        }
    }

参考資料