WebClient 사용방법

greenTea·2023년 11월 1일
1
post-thumbnail

RestTemplate -> WebClient

🤔RestTemplate의 javadoc을 보면 맨 아래 글에 RestTemplate 대신에 Webclient를 사용하도록 권장하고 있습니다.

그래서 이번기회에 WebClient에 대해서 기본적인 사용방법에 대해서만 알아보기로 했습니다.

Mono,Flux

🫡MonoFlux는 Reactor 프로젝트의 일부로, Spring WebFlux와 같은 반응형 프로그래밍을 지원하는 Spring 프로젝트에서 주로 사용됩니다. 이 두 클래스는 반응형 스트림을 나타내며, 데이터를 비동기적으로 처리할 수 있게 해줍니다.

Mono

Mono는 0 또는 1개의 결과를 나타내는 반응형 스트림입니다.
예를 들어, 데이터베이스에서 단일 항목을 조회하거나, 단일 값을 반환하는 API 호출을 할 때 Mono를 사용할 수 있습니다.
주요 메서드:

  • Mono.just(T data): 주어진 데이터로 Mono를 생성합니다.
  • Mono.empty(): 아무 데이터도 발행하지 않는 Mono를 생성합니다.
  • Mono.fromCallable(Callable<T> supplier): 주어진 Callable에서 데이터를 비동기적으로 가져옵니다.
  • Mono.subscribe(Consumer<T> consumer): 데이터나 에러를 처리하는 구독자를 추가합니다.

Flux

Flux는 0개 이상의 결과를 나타내는 반응형 스트림입니다.
예를 들어, 데이터베이스에서 여러 항목을 조회하거나, 여러 값을 반환하는 API 호출을 할 때 Flux를 사용할 수 있습니다.
주요 메서드:

  • Flux.just(T... data): 주어진 데이터로 Flux를 생성합니다.
  • Flux.fromIterable(Iterable<T> iterable): 주어진 Iterable에서 데이터를 가져옵니다.
  • Flux.range(int start, int count): 주어진 범위의 정수를 발행하는 Flux를 생성합니다.
  • Flux.subscribe(Consumer<T> consumer): 데이터나 에러를 처리하는 구독자를 추가합니다.

webclient

기존 RestTemplate

  @Test
    void test1() {
        String uri = UriComponentsBuilder.fromUriString("https://jsonplaceholder.typicode.com")
                .path("/posts/{id}")
                .buildAndExpand(1) // id 값으로 1을 설정
                .toUriString();

        RestTemplate restTemplate = new RestTemplate();
        String forObject = restTemplate.getForObject(uri, String.class);
        System.out.println(forObject);
    }

😑RestTemplate으로 get요청을 하는 코드입니다. 실행을 하게 되면 아래와 같은 결과가 나오게 됩니다.

WebClient를 이용한 비동기 HTTP 요청

 @Test
   void test2() {
       WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
       String stringMono = client.get()
               .uri("/posts/{id}",1)
               .retrieve()
               .bodyToMono(String.class)
               .block();
       System.out.println(stringMono);
   }

WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
위 코드에서는 WebClient의 인스턴스를 생성하고, 기본 URL을 설정합니다. 이렇게 설정된 URL을 기반으로 여러 요청을 수행할 수 있습니다.

String stringMono = client.get().uri("/posts/{id}",1).retrieve().bodyToMono(String.class).block();

  • get(): HTTP GET 요청을 나타냅니다.
  • uri(): 요청할 URI를 설정합니다. 여기서는 경로 변수를 사용하여 동적으로 ID 값을 넣어줍니다.
  • retrieve(): 실제 HTTP 요청을 수행합니다.
  • bodyToMono(): 응답 본문을 주어진 타입의 Mono로 변환합니다. 여기서는 문자열로 응답을 받기를 원하기 때문에 String.class를 인자로 전달했습니다. block()`: 비동기 작업을 동기적으로 처리하고, 결과 값을 반환합니다. 실제 프로덕션 환경에서는 block()을 사용하는 것을 피하고, 반응형 연산자를 통해 데이터를 처리하는 것이 좋습니다.

위 코드의 결과 값은 방금 RestTemplate으로 실행한 값과 같은 결과가 나오게 됩니다.

위 코드를 비동기 방식으로 동작하게 하려면 block()을 제거해야 합니다.

코드 변경

@Test
  void hello3() {
      WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
      client.get()
              .uri("/posts/{id}",1)
              .retrieve()
              .bodyToMono(String.class)
              .subscribe(i -> System.out.println(i));


      System.out.println("main finish");
  }

subscribe()를 통해 해당 반환값을 처리할 수 있습니다. 이렇게 되면 비동기로 값을 처리할 수 있게 되는데 위 코드를 실행하면 "main finish"라는 글만 출력이 됩니다.
비동기로 실행하였기에 이러한 상황이 발생한건데 test코드의 경우 main이 끝나버리면 전부 끝내버리기 때문에 값을 보지 못한 것입니다.
이를 위해 코드를 아래와 같이 변경하고 다시 실행해 두 값이 나오게 됩니다.

@Test
  void hello3() throws InterruptedException {
      CountDownLatch latch = new CountDownLatch(1);
      WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
      client.get()
              .uri("/posts/{id}",1)
              .retrieve()
              .bodyToMono(String.class)
              .subscribe(i -> {
                  System.out.println(i);
                  latch.countDown();
              });

      System.out.println("main finish");
      latch.await();
  }

flatMap()

flatMap()을 사용하면 마치 자바스크립트의 Promisethen()처럼 순차적으로 처리할 수 있습니다. 아래 코드를 실행해보면 바로 이해가 가실겁니다.

@Test
  void hello1() throws InterruptedException {
      CountDownLatch latch = new CountDownLatch(1);

      WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
      client.get()
              .uri("/posts/{id}", 1).accept(MediaType.APPLICATION_JSON)
              .retrieve()
              .bodyToMono(String.class)
              .flatMap(i -> {
                  System.out.println("mono1");
                  return Mono.just(i);
              })
              .flatMap(i -> {
                  System.out.println("mono2");
                  return Mono.just(i);
              })
              .subscribe(result -> {
                  System.out.println(result);
                  latch.countDown();
              }
              ,error -> error.printStackTrace());
      System.out.println("hello world");
      latch.await();
  }

보시면 순차적으로 값을 처리하는 것을 알 수 있습니다.

주의

flaMap()then()과 같이 순차적으로 처리한다면 subscribe()는 각각 처리하는 메소드입니다. 위 코드와 아래 코드를 실행해보시면서 비교해보시면 알 수 있습니다.

@Test
  void hello2() throws InterruptedException {
      CountDownLatch latch = new CountDownLatch(1);

      WebClient client = WebClient.create("https://jsonplaceholder.typicode.com");
      Mono<String> parent = client.get()
              .uri("/posts/{id}", 1).accept(MediaType.APPLICATION_JSON)
              .retrieve()
              .bodyToMono(String.class);
      parent
              .subscribe(i -> {
                  System.out.println("mono1");
              });
      parent
              .subscribe(i -> {
                  System.out.println("mono2");
              });
      parent
              .subscribe(result -> {
                          System.out.println(result);
                          latch.countDown();
                      }
                      ,error -> error.printStackTrace());
      System.out.println("hello world");
      latch.await();
  }

추가

Reactor Operators

ReactorMonoFlux는 데이터 스트림을 변환하고 조작하기 위한 다양한 연산자를 제공합니다.

Flux<Integer> flux = Flux.just(1, 2, 3, 4)
                          .map(i -> i * 2);  // 2, 4, 6, 8
  
Flux<Integer> flux = Flux.just(1, 2, 3, 4)
                          .filter(i -> i % 2 == 0);  // 2, 4
  
Flux<String> flux = Flux.just("A", "B", "C")
                         .flatMap(s -> Flux.just(s.toLowerCase(),s.toUpperCase()));  
  // "a", "A", "b", "B", "c", "C"
  
Flux<Integer> flux1 = Flux.just(1, 2);
Flux<Integer> flux2 = Flux.just(3, 4);
Flux<Integer> mergedFlux = Flux.merge(flux1, flux2);  // 1, 2, 3, 4 (순서는 보장되지 않을 수 있음)
  
Flux<String> flux1 = Flux.just("A", "B");
Flux<Integer> flux2 = Flux.just(1, 2);
Flux<Tuple2<String, Integer>> zippedFlux = Flux.zip(flux1, flux2);  // ("A", 1), ("B", 2)

참고사항

😰webflux도 분량이 방대하기에 위 내용만을 보고 사용하기에는 조금 무리가 있다고 생각합니다.(저도 현재 공부중입니다...)
토리맘의 한글라이즈 프로젝트분이 관련 글들을 자세하게 정리해놓으셔서 여길 보시면 많은 도움이 될 것 같습니다.

profile
greenTea입니다.

0개의 댓글