kagamihogeの日記

kagamihogeの日記です。

springのRestClientの使い方のメモ

https://docs.spring.io/spring-framework/reference/integration/rest-clients.html

RestTemplateの後継であるRestClientの使い方について。とはいえ、おおよそは旧来のspringユーザならお馴染みなものが多く、リファレンスを見ればだいたいは事が足りる。なので、ここでは旧来とは異なる設定方法だとかを中心に記述する。

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.4'
    id 'io.spring.dependency-management' version '1.1.7'
}


group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}


tasks.named('test') {
    useJUnitPlatform()
}

RestClientの生成

デフォルトのbuider設定のままで良ければcreate、あれこれ追加設定をするならbuilderを経由する。

RestClient defaultClient = RestClient.create();

RestClient customClient = RestClient.builder()
  .baseUrl("https://example.com")
  .build();

また、spring-bootのautoconfigがRestClient.Builderをprototype scopeでinjectionするので以下のような設定も可能。参考:https://docs.spring.io/spring-boot/api/java/org/springframework/boot/autoconfigure/web/client/RestClientAutoConfiguration.html

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;

@Component
public class RestClientBuilderSample {
  final RestClient client;

  public RestClientBuilderSample(RestClient.Builder builder) {
    this.client = builder.build();
  }
}

基底クライアントの変更

実際のHTTPアクセスには内部的に基底のHTTPクライアントライブラリが使用される。デフォルトはjava.net.http.HttpClient、対応requestFactoryはorg.springframework.http.client.JdkClientHttpRequestFactory*1となっている。

これの変更は、ドキュメントによると、ApacheやJettyであればクラスパスにそのライブラリを含めるだけで良い。たとえば、Apache HTTP Components HttpClientであれば以下となる。

// https://mvnrepository.com/artifact/org.apache.httpcomponents.client5/httpclient5
implementation("org.apache.httpcomponents.client5:httpclient5")

明示的なrequestFactoryの指定、または、factoryに追加設定が必要な場合とは以下にする。以下はApache HTTP Components HttpClientの例。

import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
RestClient client = RestClient.builder()
  .requestFactory(factory)
  .build();

それ以外のHTTPクライアントライブラリの使用方法はリファレンスを参照。Client Request Factories

factory - timeoutとかの設定

factoryでは主にtimeout値の設定変更を行う。以下はApache HTTP Components HttpClientの例。

HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setReadTimeout(1000);
factory.setConnectTimeout(1000);
factory.setConnectionRequestTimeout(1000);

RestClient client = RestClient.builder()
  .requestFactory(factory)
  .build();

注意点として、基底HTTPクライアントのfactoryにより設定可能項目が異なる。現時点ではjava.net.http.HttpClient用のfactoryにはread-timeoutのみ存在するようだ。

JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory();
factory.setReadTimeout(1000);

messageConverters - Message Converterの変更

必要なorg.springframework.http.converter.HttpMessageConverterの実装クラスを追加する。たとえば、JSON変換「のみ」にする場合は以下となる。

import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;

RestClient client = RestClient.builder()
  .messageConverters(List.of(new MappingJackson2HttpMessageConverter()))
  .requestFactory(factory)
  .build();

messageConvertersは引数がConsumerListの二種類存在する。前者はデフォルトの追加や削除など変更、後者は置換の場合に使用する。

デフォルトのMessage Converter

これを書いてるバージョンでは以下順のリストが設定されている。

  1. org.springframework.http.converter.ByteArrayHttpMessageConverter
  2. org.springframework.http.converter.StringHttpMessageConverter
  3. org.springframework.http.converter.ResourceHttpMessageConverter
  4. org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
  5. org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

XMLのMessage Converter追加

以下の依存性を追加する。jackson-dataformat-xmlの場合はmessageConvertersの明示的な指定は不要、自動的にMappingJackson2XmlHttpMessageConverterが追加される。

// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-xml
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-xml")

レスポンス

Javaオブジェクトでは無くResponseEntityで受けたい場合は以下の通り。

ResponseEntity<String> response = client
  .get()
  .uri("http://localhost:8080/a/a")
  .retrieve()
  .toEntity(String.class)

retrieveの次のメソッドで実送信

以下のようにretrieveメソッドまででは実際の送信はまだ行われず、body(Class<T>)などで行われる。

// 下記だけではまだ実際の送信は行われない。
ResponseSpec response = client
  .get()
  .uri("http://localhost:8080/a/a")
  .retrieve();

// 下記で実際の送信が行われる。
String body = response.body(String.class);

レスポンスボディが無いとか無視する場合は以下。

response.toBodilessEntity();

exchange

getpost,retrieveでは不足する場合はexchangeで細かい制御が行える。headerとかstatus-codeとかを参照して、例外投げたり結果オブジェクトの調節したり、などを行う。

import org.springframework.http.HttpRequest;
import org.springframework.web.client.RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse;

SomeResponse response = client
  .post()
  .uri("http://localhost:8080/json")
  .exchange((HttpRequest clientRequest, ConvertibleClientHttpResponse clientResponse) -> {
    HttpHeaders headers = clientResponse.getHeaders();
    HttpStatusCode statusCode = clientResponse.getStatusCode();
    String statusText = clientResponse.getStatusText();

    SomeResponse r = clientResponse.bodyTo(SomeResponse.class);
    return r;
   });

debug logging

これは基底のHTTPクライアントライブラリおよびバージョンごとに指定方法が異なる。

Apache HTTP Components HttpClient

バージョンorg.apache.httpcomponents.client5:httpclient5:5.4.xの場合はsrc/main/resources/application.propertiesに下記行を追加する。

logging.level.org.apache.hc.client5.http=DEBUG

java.net.http.HttpClient

VMオプションに以下を追加する。

-Djdk.httpclient.HttpClient.log=errors,requests,headers

参考: https://stackoverflow.com/questions/53215038/how-to-log-request-response-using-java-net-http-httpclient

はまりどころ

java.lang.ClassNotFoundException: org.apache.hc.client5.http.classic.HttpClient

Apache HTTP Components HttpClientを使うのであれば別途依存性が必要。「基底クライアントの変更」を参照。

java.lang.ClassNotFoundException: com.fasterxml.jackson.dataformat.xml.XmlMapper

xmlを扱うには別途依存性が必要。「Message Converterの変更」を参照。

参考URL

*1:正確にはjava.net.http moduleがロードされてる場合。これも存在しない場合はSimpleClientHttpRequestFactory