Java/Spring Framework

RestClient 알아보기

재심 2024. 8. 30. 14:47

목차

    [RestClient?]

    RestClient는 SpringBoot 3.2에 추가된 것으로 기존 RestTemplate을 대체하게 될 것이며 reactive 한 기능도 포함되어 있다고 한다. 

     

    소개 문서: https://spring.io/blog/2023/07/13/new-in-spring-6-1-restclient

    가이드 문서: https://docs.spring.io/spring-framework/reference/integration/rest-clients.html

     

    • RestClient는 Spring framework 6.1(Spring boot 3.2)에 새로 추가된 동기식 HTTP Client로 Spring 애플리케이션에서 REST API 호출을 위한 HTTP 요청을 보낼 수 있다.
    • RestClient의 등장으로 같은 동기식 HTTP Client인 RestTemplate을 대체하여 사용할 수 있으며, fluent API를 제공하여 현대적인 방식으로 코드를 작성할 수 있게 되었다고 한다.

     

    => 요약하면 "WebClient의 fluent API를 그대로 해서 RestTemplate의 인프라와 함께 사용할 수 있다" 라고 한다. 

     

    RestClient의 등장 이유

    기존의 RestTemplate는 2009년 Spring 3.0에 추가된 상당히 오래된 동기식 HTTP Client이다. 그로 인해 몇 가지 단점들이 존재한다.

     

    • RestTemplate는 수많은 메서드가 오버로딩되어 제공하기 때문에 기능을 사용하는데 혼란을 줄 수 있다.
    • 고전적인 방식인 Template method 패턴을 활용한 클래스로 현대적인 방식과는 거리가 멀다.
    • Non-Blocking 환경에서는 적절하지 않다.

    이후 등장한 WebClient는 동기식 처리와 비동기식 처리 모두 지원하며, fluent API를 제공한다. 하지만 Spring MVC 환경에서 사용하기 위해 WebFlux를 추가로 의존해야 한다는 단점이 있다.

    따라서, Spring MVC 환경에서 현대적인 방식으로 HTTP Client 사용하기 위해 등장했다.

     

    RestTemplate vs WebClient vs RestClient 구현 방식 비교

    WebClient를 보면 체이닝 방식으로 기능을 구현하고 있다. 반면 RestTemplate은 오래된? 라이브러리 답게 구현이 깔끔해 보이지는 않는다. 

    RestClient의 경우 현대적인.. 체이닝 방식으로 코드를 작성할 수 있는 모습이다.

     

    // RestTemplate
    RestTemplate restTemplate = new RestTemplate();
    HttpHeaders headers = new HttpHeaders();
    MediaType mediaType = new MediaType("application", "json", StandardCharsets.UTF_8);
    headers.setContentType(mediaType);
    HttpEntity<String> entity = new HttpEntity<>("{}", headers);
     
    String response = restTemplate.postForObject("http://localhost:8080", entity, String.class);
     
    // WebClient
    WebClient.create("http://localhost:8080")
            .post()
            .uri("/")
            .accept(MediaType.APPLICATION_JSON)
            .exchangeToMono(response -> response)
            .block();
     
    //RestClient
    int id = ...;
    Pet pet = restClient.get()
      .uri("https://petclinic.example.com/pets/{id}", id)
      .accept(APPLICATION_JSON)
      .retrieve()
      .body(Pet.class);

     

    WebClient를 사용하기 위해서는 WebFlux에 의존성이 생기고, WebFlux는 reactor기반의 구현이기 때문에 reactor에 대한 지식이 부족하다면 러닝커브도 있다. 

    https://docs.spring.io/spring-framework/reference/web/webflux-webclient/client-synchronous.html

     

    With Flux or Mono, you should never have to block in a Spring MVC or Spring WebFlux controller. Simply return the resulting reactive type from the controller method. The same principle apply to Kotlin Coroutines and Spring WebFlux, just use suspending function or return Flow in your controller method .

    Flux 또는 Mono를 사용하면 Spring MVC 또는 Spring WebFlux 컨트롤러에서 차단할 필요가 없습니다. 컨트롤러 방식에서 결과 반응형을 반환하기만 하면 됩니다. Kotlin Coroutines 및 Spring WebFlux에도 동일한 원리가 적용되며 컨트롤러 방식에서 Suspending 함수를 사용하거나 Flow를 반환합니다.

     

    => RestClient는 WebClient의 간단한 구현 방식을 차용하면서 RestTemplate의 기능을 그대로 가져온다고 설명하고 있다.

     

    [클라이언트 생성 방법]

    RestClient도 내부적으로 어떤 HttpClient사용하여 통신할 것인지 결정할 수 있다.

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

     

    • JdkClientHttpRequestFactory: 자바 기본 HttpClient (java.net.http) 
    • HttpComponentsClientHttpRequestFactory: Apache HttpClient
    • JettyClientHttpRequestFactory: Jetty’s HttpClient
    • ReactorNettyClientRequestFactory: Reactor Netty’s HttpClient
    • SimpleClientHttpRequestFactory: Simple HttpClient (java.sun에서 제공하는 Client 일 듯)

    이 때 Apache나 Jetty HttpClient 의존성이 설정되어 있다면 설정된 HttpClient를 사용한다고 하며, 의존성이 없으면 자바 기본 HttpClient를 사용한다고 한다. 

     

    무엇을 쓰면 좋을지.. GPT에게 물어보았다. (다 믿지말고.. 참고만 한다) 

    (OkHttp는 지원 대상에 없는 것 같으니 참고로만 읽는다)

     

    1. JDK HttpClient (Java 11+)
    장점:표준 라이브러리: 추가적인 라이브러리 의존성이 필요하지 않습니다.HTTP/2 지원: 기본적으로 HTTP/2를 지원합니다.간결성: Java 표준 API로서 쉽게 접근할 수 있습니다.단점:기능적으로는 제한적일 수 있으며, 고급 기능(예: 커넥션 풀링, 고급 리트라이 정책)이 부족할 수 있습니다.추천 상황:애플리케이션이 비교적 간단하거나 추가적인 라이브러리 의존성을 피하고 싶은 경우.Java 11 이상을 사용하고 있으며, 기본적인 HTTP/2 지원이 필요한 경우.

    2. Apache HttpClient
    장점:강력한 기능: 매우 세밀한 HTTP 요청 제어와 다양한 설정 옵션을 제공합니다.성숙도: 오래된 프로젝트로서, 매우 안정적이고 널리 사용됩니다.플러그인 가능: 다양한 플러그인을 통해 확장할 수 있습니다.단점:설정이 다소 복잡할 수 있으며, 애플리케이션 크기가 커질 수 있습니다.추천 상황:고급 HTTP 기능이 필요하거나, 대규모 애플리케이션에서 안정적이고 세밀한 제어가 필요한 경우.커넥션 풀링 및 타임아웃 관리가 중요한 경우.

    3. OkHttp
    장점:경량화: 가벼운 라이브러리로서, 빠른 성능과 적은 메모리 소비가 특징입니다.HTTP/2 및 HTTP/3 지원: 최신 프로토콜을 지원합니다.편리한 API: 설정과 사용이 간편하며, 다양한 유틸리티를 제공합니다.단점:Java보다는 Kotlin 생태계에서 더 자주 사용됩니다.추천 상황:성능이 중요한 모바일 애플리케이션 또는 경량화된 서버 애플리케이션에서 유용합니다.HTTP/2 이상의 최신 프로토콜을 활용하려는 경우.

    4. Jetty HttpClient
    장점:비동기 처리에 최적화: 넌블로킹 I/O와 비동기 처리가 뛰어납니다.고성능: 고부하 환경에서도 안정적으로 동작합니다.웹 서버와 클라이언트 통합: Jetty 서버와의 통합성이 뛰어납니다.단점:Jetty 생태계에 익숙하지 않다면, 초기 학습이 필요할 수 있습니다.추천 상황:고성능 비동기 처리 또는 Jetty 기반의 웹 애플리케이션에서 유용합니다.서버와 클라이언트 간 통합이 필요한 경우.

    추천 결론
    - 일반적인 애플리케이션: Java 11 이상을 사용하며, 추가적인 설정이나 특수한 기능이 필요하지 않은 경우, JDK HttpClient를 사용하는 것이 간편하고 유지보수하기 쉬운 선택입니다.
    - 고급 기능 필요: 커넥션 풀링, 고급 리트라이 로직, 확장성 있는 설정 등을 요구하는 애플리케이션에서는 Apache HttpClient를 추천합니다.
    - 경량화 및 최신 프로토콜: 모바일 애플리케이션 또는 최신 HTTP/3 등의 프로토콜을 활용하려는 경우 OkHttp가 좋은 선택입니다.
    - 비동기 고성능 처리: 비동기 작업이 많고, 고성능을 요구하는 경우 Jetty HttpClient를 고려해볼 수 있습니다.

    애플리케이션의 특성과 요구 사항에 따라 최적의 HttpClient를 선택하면 됩니다. 각 클라이언트의 장점과 단점을 고려하여 선택하세요.

     

    [RestClient 구동 방식 이해해보기]

    RestClient도 내부적으로 당연..하겠지만 HttpClient를 사용하여 http 호출을 하게 된다. 

    retrieve() 하게 되면 실제로 http 호출이 내부적으로 발생하게 된다. 

     

    ResponseEntity<String> response = restClient.post()
        .uri(urlBuilder -> urlBuilder.path("path")
            .queryParam("delayMs", delayMs)
            .build(seq)
        )
        .body(context)
        .retrieve()
        .toEntity(String.class);

     

    retrieve를 하면 exchangeInternal을 호출

     

    내부적으로 http 호출에 필요한 값들을 세팅하고, clientRequest.execute()를 하여 http 호출을 하게 된다.

     

     

    clientRequest는 springframework에서 제공하는 인터페이스이다.

    구현체는 3개가 있는데, 별다른 설정을 하지 않았으면 AbstractClientHttpRequest 구현체가 사용된다. 

     

    내부구현을 또 보면.. executeInternal을 호출함 

     

    AbstractStreamingClientHttpRequest 사용 

     

    body 데이터를 기록하고.. 다시 executeInternal 호출

     

    우리는 JdkClientHttpClient를 선택했으므로 JdkClientHttpRequest 구현체가 선택된다. 

    타임아웃을 설정했으면 sendAsync를 호출하고, 그렇지 않으면 send를 호출한다. 

    이 때 어떤 httpClient를 사용하는지 봐야 하는데, java.net의 httpClient를 사용하고 있다. (구현체는 HttpClientImpl이다.)

    (OpenFeign에서는 기본적으로 sun에서 구현한 httpClient를 사용하였었음)

     

    그리고 이 HttpClient의 Factory 정의를 살펴보면 httpClient도 지정하지만 별도의 Executor도 지정할 수 있는 모습이다. 

    즉 httpClient를 수행할 때 별도의 Executor를 통해 실행하도록 지원한다. (아마도 http 호출도 비동기적으로 처리할 수 있도록 뭔가..지원하기 위함인듯 하다) 

     

    디폴트 생성자로 지정하면 HttpClient.newHttpClient() 가 호출된다. 

    타고타고 가다보면.. executor를 지정하지 않았을 때 Executor.newCachedThreadPool()을 통해 스레드를 사용한다. 

     

    ** newCachedThreadPool

    • 동적 스레드 수 조절: 이 스레드 풀은 필요한 만큼 스레드를 생성합니다. 만약 스레드가 유휴 상태로 일정 시간이 지나면, 해당 스레드는 종료되기 때문에 새로운 작업이 들어오면 필요할 때 새로운 스레드를 생성할 수 있습니다.
    • 무제한 스레드 수: 이 스레드 풀은 기본적으로 무제한의 스레드 수를 허용합니다. 즉, 작업 큐에 들어오는 작업의 수가 많아질수록 스레드를 계속 생성할 수 있습니다.
    • 유휴 스레드 유지: 스레드 풀의 스레드는 60초 동안 유휴 상태로 있으면 종료됩니다. 즉, 60초 동안 작업을 처리하지 않으면 스레드는 종료되고, 필요한 경우 새로운 스레드를 생성합니다.
    • 작업 큐: 이 풀은 SynchronousQueue를 사용합니다. 이 큐는 작업을 처리할 스레드가 없을 때 새 스레드를 생성하고, 스레드가 작업을 처리하면 큐에서 작업을 제거합니다. 이 때문에 대기 큐가 없고 작업이 들어오는 즉시 스레드가 작업을 처리합니다.

     

    => 스레드를 필요한 만큼 생성하고, 60초 동안 사용하지 않으면 제거하는 특징으로 요약할 수 있다.

     

    즉, JDKHttpClient를 사용할 때 별다른 설정을 하지 않으면 http 호출을 할 때마다 새로운 스레드를 생성하여 호출하게 된다. 

     

     

    어쩃든 한 번 정리해보면 RestClient를 사용할 때 원하는 HttpClient를 사용할 수 있는데, java에서 기본적으로 제공하는 HttpClient를 사용할 경우 비동기 지원을 위한 별도의 executor를 설정할 수 있고 별도로 지정하지 않으면 newCachedThreadPool 로 스레드풀을 초기화한다. 

    (OpenFeign에서 기본적으로 사용하는 HttpClient와도 다르다는 것을 알 수 있다) 

     

     

    다시 본론으로 돌아오면 http 호출을 하면 httpClientImpl의 send를 호출하게 되고, 내부적으로는 sendAsync를 호출하여 결과 타입으로 CompletableFuture를 통해 비동기적인 결과를 받아 처리하게 된다. 

     

     

    sendAsync를 하게 되면 httpRequestImpl 객체를 만들어 http 호출을 하기 위한 준비를 하고.. 

    MultiExchange라는 객체를 통해 실제로 요청을 날리는 듯 하다. 

     

    지정한 executor와 함께 비동기적으로 http 호출을 날리고 CompletableFuture를 바로 반환한다. 

     

     

    ...나머지 부분은 생략..

     

    어쨋든 결과적으로.. 이렇게 호출해서 response를 받아오는 구조이다.

     

    'Java > Spring Framework' 카테고리의 다른 글

    Annotation 정리  (0) 2022.10.30
    Bean 생성  (0) 2022.10.30
    Auto Configuration  (0) 2022.10.30