Spring WebClient

Soonwoo Kwon·2023년 5월 29일
0

Spring WebClient

Webclient는 Spring 5.0 부터 지원되며, API 호출에 이용되는 인터페이스이다.
기존 Spring에서 활용되던 RestTemplate과 동일한 역할을 한다.

하지만 RestTemplate은 현재 deprecated 된 상태이며 Webflux로의 권한을 권장한다.

RestTemplate VS WebClient

RestTemplate과 WebClient 차이점과 WebClient의 장점을 알아보자.

통신 방법

동기블로킹
RestTemplate동기블로킹
WebClient비동기(동기)논블로킹

RestTemplate은 동기 및 블로킹 통신만을 지원하는 반면
WebClient는 비동기 및 non-blocking 통신을 지원하고, 동기적인 통신 또한 지원 가능하다.

RestTemplate(동기 & 블로킹)

RestTemplate은 Multi-thread를 이용한 동기 & 블로킹 통신 방법만을 지원한다.

  • 설정값에 따라 지정된 개수의 스레드풀을 등록하고, 요청이 발생할 때마다 각 요청에 하나의 스레드가 할당된다. 각 요청은 QUEUE에 적재되고, 가용한 스레드에게 할당된다.
  • 동기 & Blocking 방식으로 동작하기 때문에, 각 스레드는 요청에 대한 응답이 처리될 때 까지 다른 요청을 수행할 수 없다.
  • 따라서 요청이 많아질 수록 QUEUE에 대기중인 요청이 많아지고, 서비스 응답 시간이 증가하게 된다.

WebClient(비동기 & 논블로킹)

WebClient는 Single-thread를 이용한 비동기 & 논블로킹 통신을 지원한다.

  • Clients의 요청은 Event Loop에 등록되며 Workers 에게 요청을 제공한다.
  • Event Loop은 Workers로 부터의 응답이 반환되지 않더라도 논블로킹 방식으로 다른 요청을 수행할 수 있다.
  • Workers로 부터의 완료된 요청은 callback을 통해 전달되며, 해당 응답을 Clients에게 전달한다.

WebClient의 사용법

인스턴스 생성

create()

WebClient client = WebClient.create();
WebClient client = WebClient.create("http://localhost:8080");

create() 메서드를 통해 WebClient 인스턴스를 생성할 수 있으며, URI를 지정하는 메서드도 제공한다.

builder()

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()

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);

Mono VS Flux

onStatus()

또한 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()

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/

출처

0개의 댓글