[Spring]WebClient를 이용한 REST API 호출 Spring Framework

Spring에서 REST API를 호출하기 위한 라이브러리로 RestTemplate이 있는데 이 녀석은 동기 버전이기 때문에 Webflux와 같이 사용하기 어려워 문서를 찾다보니 WebClient가 있어 사용해 보았다.

참고로 JAVADOC에 보면 RestTemplate에 대해서는 다음처럼 NOTE가 있는데

NOTE: As of 5.0, the non-blocking, reactive org.springframework.web.reactive.client.WebClient offers a modern alternative to the RestTemplate with efficient support for both sync and async, as well as streaming scenarios. The RestTemplate will be deprecated in a future version and will not have major new features added going forward. See the WebClient section of the Spring Framework reference documentation for more details and example code.

요약하면 RestTemplate은 deprecated될 예정이며 더 나은 체계를 가지는 WebClient가 있으니까 그걸 써라 정도의 의미로 보인다.(비동기 버전인 AsyncRestTemplate은 이미 deprecated되어 있음) WebClient에서 동기형태로 호출할 수 있으니 RestTemplate도 대체가 가능하긴한데 그건 아래 코드에서 살펴보기로 한다.

WebClient로 비동기 호출을 했을때 RestTemplate의 동기 호출에 대해서 다르거나 좋은 점은 다음과 같다.

1. WebClient는 Netty등의 비동기 통신 라이브러리를 기반으로 한다.
2. 이벤트 루프 구조를 사용하므로 최대 스레드에 대한 관리등의 고민을 할 필요가 없다. 스택오버플로에서 WebClient에 대한 max threads에 대한 문의에 다음과 같은 코멘트가 있는데

Spring WebClient is a No-Blocking IO http client while ReactorClientHttpConnector is a Reactor-Netty based implementation. Said that I can suggest to do not warry about connection pool but focus on a complete no blocking service call. 

WebClient는 비동기 Netty 기반이니까 커넥션 풀에 대한 고민은 하지 말라는 이야기고 구조가 다르기 때문에 RestTemplate의 스레드 관련 설정 값도 없다.

3. 기본적으로 비동기 처리이므로(동기 형태로도 가능) REST 호출 후 추가적으로 기다릴 필요가 없을 때나 다른 처리와의 의존성이 없을때 다른 처리가 이 REST 호출을 기다리지 않아도 된다.

WebClient를 사용하기 위해서는 Gradle 기준으로 다음의 의존성을 요구한다. 

compile 'org.springframework.boot:spring-boot-starter-webflux'
compile 'io.projectreactor:reactor-test'
의존성의 내용처럼 WebClient는 WebFlux(Spring의 비동기 HTTP 처리)와의 연동을 전제로 구성되어있고 기본적인 동작은 비동기 처리를 전제로 한다.

WebClient는 builder를 통해 생성하며 builder는 thread-safe 하지 않으므로 각 호출 혹은 Injection Point에 대해서 별도의 인스턴스를 생성해야 한다. 때문에 RestTemplate을 Bean으로 만들어 사용하는 것과 가장 비슷하게 사용하려면 다음처럼 코딩해서 사용하면 된다. webflux에 대한 의존성을 추가하면 WebClient.Builder의 의존성은 Spring이 처리해주므로 Builder에 대한 Bean은 직접 생성하지 않아도 된다.

@Service
public class TestService {
    private final WebClient webClient;

    public TestService(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.baseUrl("http://www.test.com").build();
    }

...

이렇게 생성한 webClient는 다음처럼 비동기 호출하여 사용할 수 있다.

long userId = 10;

        Mono<String> result = webClient
                .get()
                .uri(String.format("/getuser", userId))
                .retrieve()
                .bodyToMono(String.class);

        result.subscribe(response -> {
System.out.println("response : " + response);
        }, e -> {
            System.out.println("error message : " + e.getMessage());
        });
위의 호출에서는 단순히 응답 내용을 얻기 위해서 rerieve()를 사용했지만 exchange()를 사용하면 헤더 등의 부가 정보에 접근할 수 있다. 비동기 호출이므로 WebClient의 반환값은 Mono<T>로 받아 subscribe로 처리하지만 동기의 경우에는 block() 메서드를 사용하면 RestTemplate 처럼 동기식으로 처리할 수 있다.

        String result = webClient
                .get()
                .uri(String.format("/getuser", userId))
                .retrieve()
                .bodyToMono(String.class).block();
System.out.println("response : " + result);

덧글

댓글 입력 영역