Webclient는 Spring 5.0 부터 지원되며, API 호출에 이용되는 인터페이스이다.
기존 Spring에서 활용되던 RestTemplate과 동일한 역할을 한다.
하지만 RestTemplate은 현재 deprecated 된 상태이며 Webflux로의 권한을 권장한다.
RestTemplate과 WebClient 차이점과 WebClient의 장점을 알아보자.
동기 | 블로킹 | |
---|---|---|
RestTemplate | 동기 | 블로킹 |
WebClient | 비동기(동기) | 논블로킹 |
RestTemplate은 동기 및 블로킹 통신만을 지원하는 반면
WebClient는 비동기 및 non-blocking 통신을 지원하고, 동기적인 통신 또한 지원 가능하다.
RestTemplate은 Multi-thread를 이용한 동기 & 블로킹 통신 방법만을 지원한다.
- 설정값에 따라 지정된 개수의 스레드풀을 등록하고, 요청이 발생할 때마다 각 요청에 하나의 스레드가 할당된다. 각 요청은 QUEUE에 적재되고, 가용한 스레드에게 할당된다.
- 동기 & Blocking 방식으로 동작하기 때문에, 각 스레드는 요청에 대한 응답이 처리될 때 까지 다른 요청을 수행할 수 없다.
- 따라서 요청이 많아질 수록 QUEUE에 대기중인 요청이 많아지고, 서비스 응답 시간이 증가하게 된다.
WebClient는 Single-thread를 이용한 비동기 & 논블로킹 통신을 지원한다.
- Clients의 요청은 Event Loop에 등록되며 Workers 에게 요청을 제공한다.
- Event Loop은 Workers로 부터의 응답이 반환되지 않더라도 논블로킹 방식으로 다른 요청을 수행할 수 있다.
- Workers로 부터의 완료된 요청은 callback을 통해 전달되며, 해당 응답을 Clients에게 전달한다.
WebClient client = WebClient.create();
WebClient client = WebClient.create("http://localhost:8080");
create() 메서드를 통해 WebClient 인스턴스를 생성할 수 있으며, URI를 지정하는 메서드도 제공한다.
WebClient client = WebClient.builder()
.baseUrl("http://localhost:8080")
.defaultCookie("cookieKey", "cookieValue")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
.build();
빌더를 이용할 수 있으며 요청의 기본적인 Header, Cookie 등의 default 값을 설정하고,
해당 객체를 빈으로 등록하여 재사용하기도 한다.
WebClient.RequestHeadersSpec<?> requestHeaderSpec = client.post() // 메서드 정의
.uri("/persons/{id}", id) // URI 정의
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) // header 정의
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.body(personMono, Person.class) // RequesetBody 정의
builder 패턴을 이용하여 요청의 메서드, 헤더, URI 등을 지정할 수 있다.
retrieve는 응답 body를 디코딩하는 메서드이다.
다음과 같이 body를 디코딩하여 응답 body를 Mono 또는 Flux로 디코딩할 수 있다.
Mono(0~1개)
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Person.class);
Flux(0~N개)
Flux<Quote> result = client.get()
.uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(Quote.class);
또한 onStatus() 메서드를 통해 예외 상황에 대한 분기도 처리할 수 있다.
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> ...)
.onStatus(HttpStatus::is5xxServerError, response -> ...)
.bodyToMono(Person.class);
retrieve() 에선 기본적으로 4XX
, 5XX
응답 코드에 대해 WebClientResponseException.BadRequest
WebClientResponseException.NotFound
예외를 발생시킨다.
exchange() 메서드는 응답 body가 아닌 더 상위의 ClientResponse에 접근한다.
Mono<Person> result = client.get()
.uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
.exchange()
.flatMap(response -> response.bodyToMono(Person.class));
exchange() 메서드는 retrieve() 와는 다르게 4XX
, 5XX
응답에 대한 예외처리를 제공하지 않기 때문에, 모든 상태 코드에 대한 처리를 고려해야 한다.
block() 메서드를 통해 동기 통신 결과를 생성할 수 있다.
Person person = client.get().uri("/person/{id}", i).retrieve()
.bodyToMono(Person.class)
.block();
List<Person> persons = client.get().uri("/persons").retrieve()
.bodyToFlux(Person.class)
.collectList()
.block();
- block() 메서드를 사용하게 되면 요청의 결과를 Reactive Stream 인 Mono나 Flux가 아닌 객체(객체 리스트)로 생성하게 된다.
- 즉 동기적으로 해당 요청의 결과가 응답되기를 대기하는 상태가 된다.
- 따라서 WebClient의 비동기적 통신을 효과적으로 이용하기 위해서 block() 메서드 사용에 주의가 필요하다.
- 참고: https://tecoble.techcourse.co.kr/post/2021-10-20-synchronous-asynchronous/