概要
RESTなAPIにアクセスするのはRestTemplateではなくWebClientが今後推奨となるため、WebClientの概要についてまとめた。
WebClientを使用するための準備と、どんなメソッドがあるのかを紹介している。
前提
今後動作確認を行う際には、以前の記事で作成したREST APIアプリにリクエストを行ってレスポンスを取得する。
※今回のMavenプロジェクトの作成についても、以下をベースとしている
概要 これから数回にかけてREST APIについて学んだことを載せていく。 今回はREST APIの仕組みとプロジェクト作成方法について紹介する。 尚、RESTとは何かということについては取り扱わない。 仕組み[…]
WebClient
WebClientは、Spring Framework5以降で導入された非同期・リアクティブなHTTPクライアントとなる。
REST APIや外部サービスとHTTP経由で通信する際に使用し、GET/POST/PUT/DELETE などのHTTPメソッドを柔軟に扱える。
※SpringMVCでは一律同期的に処理を行うため、以降の記事では非同期の処理については扱わない
基本的な使い方
WebClientでは、以下のようにメソッドチェーンを使用して柔軟にAPI通信を行う。(※GET通信の例)
.uri(URI情報)
.retrieve()
.bodyToMono(取得したいレスポンスの型)
.block();
GET通信を行うメソッド。(「webClient」は、WebClientのインスタンス)
この段階ではリクエストはまだ実施せず、内部型のWebClient.RequestHeadersUriSpecオブジェクトを返却する。
リクエストの URI を設定する。
WebClient.RequestHeadersSpec<?> を返し、ヘッダー設定や送信メソッドが利用可能になる。
リクエストを送信し、レスポンスを扱うWebClient.ResponseSpecを返却する。
レスポンスボディを非同期で扱う Mono<取得したい型> を返す。
※上記のメソッドは、レスポンスボディのみ(ステータスやヘッダーは含まない)を扱う
非同期処理が完了するまで待機し、同期的な戻り値として取得する。
Spring MVCでは、基本的にこのメソッドで同期処理に変換する。
基本的なメソッド
WebClientは内部型オブジェクトとMonoオブジェクトを使用してAPI通信を行う。
以下にWebClientにてメソッドチェーンで使用する基本的なメソッドを紹介する。
メソッド | 説明 |
---|---|
WebClient.get() | GET通信を行う |
WebClient.post() | POST通信を行う |
WebClient.put() | PUT通信を行う |
WebClient.delete() | DELETE通信を行う |
UriSpec.uri(URI情報) | URI情報を設定する |
RequestBodySpec.bodyValue(リクエストボディ) | POSTやPUT通信に使用するリクエストボディを設定する |
RequestBodySpec.contentType(コンテンツタイプ) | Content-Typeヘッダーを設定する |
RequestHeadersSpec.headers(ヘッダー情報) | リクエストのヘッダー情報を設定 |
RequestHeadersSpec.retrieve() | リクエスト送信を行い、ResponseSpecを取得する |
RequestHeadersSpec.exchangeToMono() | リクエスト送信を行い、ClientResponseを変換してMono<T>を取得する ※ClientResponseはヘッダー/ステータス/ボディにアクセス可能なオブジェクト |
ResponseSpec.bodyToMono(レスポンス型) | リクエスト送信を行い、Mono<T>を取得する |
Mono<T>.block() | 同期的にT型のレスポンスを取得する |
事前準備
WebClientを使用するための事前準備を行う。
必要ライブラリ
WebClientまわりの機能を利用するため、必要な資材をpom.xmlに追加する。
pom.xml
<dependencies>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- Spring Web MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.30</version>
</dependency>
<!-- Spring WebFlux -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.3.30</version>
</dependency>
<!-- Project Reactor Core -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.4.34</version>
</dependency>
<!-- Project Reactor Netty -->
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>1.0.39</version>
</dependency>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- Logback Classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
<!-- Jackson Datatype JSR310 for Java Time API -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
WebClientを使用するためには以下の資材が必要となる。
・Project Reactor Core: Monoオブジェクトなどの機能
・Project Reactor Netty: 非同期HTTP通信の機能
Bean定義
WebClientをDIコンテナから取得するため、Bean定義を行う。
簡潔に記述できるため、Javaベースで作成する。
WebClientConfig.java
package com.example.webclient_prototype.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
/**
* WebClientのBean定義
*/
@Configuration
public class WebClientConfig {
@Bean
public HttpClient httpClient() {
return HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) // 接続タイムアウト(5秒)
.responseTimeout(Duration.ofSeconds(10)); // レスポンス全体のタイムアウト(10秒)
}
@Bean
public ReactorClientHttpConnector reactorClientHttpConnector(HttpClient httpClient) {
return new ReactorClientHttpConnector(httpClient);
}
@Bean
public WebClient webClient(ReactorClientHttpConnector reactorClientHttpConnector) {
return WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE + ", " + MediaType.APPLICATION_PROBLEM_JSON_VALUE)
.clientConnector(reactorClientHttpConnector)
.build();
}
}
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE + “, ” + MediaType.APPLICATION_PROBLEM_JSON_VALUE)
WebClientを使用して行うすべてのHTTPリクエストに、共通のHTTPヘッダーを設定している。
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- コンポーネントスキャン -->
<context:component-scan base-package="com.example.webclient_prototype.biz" />
<!-- WebClientのBean定義 -->
<bean class="com.example.webclient_prototype.config.WebClientConfig"/>
</beans>
<bean class=”com.example.webclient_prototype.config.WebClientConfig”/>
定義したJavaベースのBean定義ファイルをDIコンテナに登録する。
※コンポーネントスキャン対象のパッケージ配下に格納していれば不要
APIアクセスクラス
WebClientのDIを行い、APIにアクセスするメソッドを今後追加していく。
WebApiClient.java
package com.example.webclient_prototype.biz;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
/**
* WebClientを使用したAPI疎通クラス
*/
@Component
public class WebApiClient {
@Autowired
private WebClient webClient;
}
リソースクラス
REST APIアプリから返却されるレスポンスを、リソースクラスにマッピングするため用意する。
Resource.java
package com.example.webclient_prototype.resource;
import java.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* リソースクラス
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Resource {
/** ID */
private String id;
/** 名前 */
private String name;
/** とある日付 */
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate hogeDate;
}
APIエラー情報クラス
例外発生時(4xx系や5xx系)に返却されるレスポンスは、以下のクラスにマッピングする。
ApiErrorInfo.java
package com.example.webclient_prototype.resource.error;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* APIエラー情報クラス
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class ApiErrorInfo {
private Integer status;
private String errorTitle;
private String errorMsg;
private String errorCode;
private List<Item> errors;
@Data
private static class Item {
private String target;
private String msg;
}
}
動作確認用クラス
WebClientの動作を簡易的に確認するため、エントリーポイントとなるクラスを用意する。
Main.java
package com.example.webclient_prototype.executor;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.webclient_prototype.biz.WebApiClient;
public class Main {
public static void main(String[] args) {
try (var context = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext.xml")) {
// DIコンテナから取得
var client = context.getBean(WebApiClient.class);
}
}
}
自作例外クラス
WebClientはRestTemplateのようにHTTPステータスごとに細かい例外クラスが用意されておらず、4xx系や5xx系のエラーはすべてWebClientResponseExceptionとして処理される。
このため、例外発生時にはステータスコードを元に条件分岐を行う必要がある。
ステータスごとの処理を明確にして@ExceptionHandler側の実装をシンプルに保つために、WebClientResponseExceptionを自作の例外クラスに変換してスローする方針としている。
今後は、例外ハンドリングを行う際にはこの自作例外クラスを適切にスローしていく。
※実際に@ExceptionHandlerを用いたハンドリングについては既に別の記事で扱っているため、WebClientまわりの記事では割愛する
ClientErrorException.java
package com.example.webclient_prototype.exception;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
// 4xx系エラー
@Getter
@Setter
@ToString
public class ClientErrorException extends RuntimeException {
private Integer status;
private String title;
private String detailMessage; // toStringで表示するためだけに追加
public ClientErrorException(String message) {
this(null, message, null);
}
public ClientErrorException(String title, String message) {
this(title, message, null);
}
public ClientErrorException(String title, String message, Integer status) {
super(message);
this.title = title;
this.status = status;
this.detailMessage = message;
}
}
ServerErrorException.java
package com.example.webclient_prototype.exception;
// 5xx系エラー
public class ServerErrorException extends RuntimeException {
public ServerErrorException(String message) {
super(message);
}
}
UnknownErrorException.java
package com.example.webclient_prototype.exception;
// 想定外エラー
public class UnknownErrorException extends RuntimeException {
public UnknownErrorException(String message) {
super(message);
}
}
まとめ
☑ RestTemplateは今後機能追加がないため、WebClientの使用が推奨される
☑ WebClientはメソッドチェーンで柔軟なAPI通信を実装できる
☑ SpringMVC環境では、 WebClientを同期的に使用する