WebClient 는 Spring 에서 제공하는 RestClient 의 한 종류이다. 과거에 사용되던 RestTemplate 과 비슷한 역할이라고 생각하면 되는데 RestTemplate 는 장기적으로 Deprecated 예정이기 때문에 이제는 WebClient 를 사용해야 한다.
WebClient 의 가장 큰 특징은 RestTemplate 과 다르게 논블로킹을 지원한다는 것이다. 말만 들으면 성능에 굉장한 이득이 있을 것 같지만 논블로킹 방식으로 사용하지 않으면 RestTemplate 을 사용하는 것과 차이가 없다.
WebClient 의 응답값은 Mono 또는 Flux 타입이다. Mono, Flux 는 리액티브 스트림을 구성하는 파이프라인의 일부로 웹플럭스에서는 기본적으로 논블로킹 처리되지만 SpringMVC 환경에서 사용시에는 논블로킹 처리를 수동으로 구성해줘야 한다.
우선 Mono, Flux는 결과를 미래에 알 수 있다는 점에서 Future와 비슷하지만 Future 는 이미 시작되었음을 의미하지만 Mono, Flux 는 시작할 수 있음을 의미한다는 점을 인지하고 있어야 한다.
즉, WebClient의 호출을 단순히 Mono, Flux 을 리턴하도록 하고 끝낸다면 대상 서버를 호출조차 하지 않는다는 말이다.
Mono<Integer> mono = webClient.get().uri("/test1")
.retrieve()
.bodyToMono(Integer.class);
Flux<Integer> flux = webClient.get().uri("/test2")
.retrieve()
.bodyToFlux(Integer.class);
이걸 어떻게 처리할지 추가 정의해야 작업이 시작 되는데 블로킹방식과 논블로킹 방식으로 처리할 수 있고 블로킹 방식은 아래와 같이 사용하면 된다.
int result = webClient.get().uri("/test1")
.retrieve()
.bodyToMono(Integer.class)
.block();
List<Integer> resultList = webClient.get().uri("/test2")
.retrieve()
.bodyToFlux(Integer.class)
.toStream()
.collect(Collectors.toList());
문제는 이는 블로킹 방식으로 RestTemplate 를 사용할 때와 성능상 차이가 없고 성능 향상을 위해서는 논블로킹 방식으로 사용할 필요가 있다.
논블로킹 방식으로 사용하는 방법은 아래와 같다.
AtomicInteger result = new AtomicInteger();
List<Integer> resultList = new ArrayList<>();
webClient.get().uri("/test1")
.retrieve()
.bodyToMono(String.class)
.subscribe(e -> result.set(e));
webClient.get().uri("/test2")
.retrieve()
.bodyToFlux(String.class)
.subscribe(e -> resultList.add(e));
위 코드에서 2번의 WebClient 호출은 논블로킹으로 동작한다. 이에 메인스레드가 종료되면 완료여부와 무관하게 곧바로 종료된다.
해서 메인스레드 입장에서 각 호출의 처리완료를 기다려야 하고 일반적으로 사용되는 방식은 아래와 같이 CountDownLatch 를 이용한 방법이다.
CountDownLatch cdl = new CountDownLatch(2);
AtomicInteger result = new AtomicInteger();
List<Integer> resultList = new ArrayList<>();
webClient.get().uri("/test1")
.retrieve()
.bodyToMono(String.class)
.doOnTerminate(() -> cdl.countDown())
.subscribe(e -> result.set(e));
webClient.get().uri("/test2")
.retrieve()
.bodyToFlux(String.class)
.doOnTerminate(() -> cdl.countDown())
.subscribe(e -> resultList.add(e));
cdl.await();
어차피 처리결과를 기다려야 한다면 블로킹이 발생하게 되는데 무엇이 이득이냐고 생각될 수 있지만 메인스레드 입장에서는 블로킹이지만 각각의 WebClient 호출은 논블로킹으로 호출된다.
이에 각각의 호출이 5초가 소요된다고 가정하면 WebClient 를 블로킹 방식으로 사용한다면 총 처리시간은 10초가 될테지만 위와 같이 논블로킹 방식으로 사용한다면 총 처리시간은 5초가 된다.
참고로 WebClient를 코루틴에서 사용할 경우 Mono와 Flux 를 그대로 사용하진 못하고 변환을 해야 하는데 각각 아래와 같이 대체하면 된다.
bodyToMono == awaitBody
bodyToFlux == bodyToFlow
코루틴에서 역시 메인스레드에서 처리의 완료를 기다려야 하는데 runBlocking 블록을 사용하면 되고 launch, async 블록들을 활용해서 논블로킹 처리가 가능하다.
https://stackoverflow.com/questions/59607165/what-is-the-difference-between-block-subscribe-and-subscribe
https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client
https://doinge-coding.tistory.com/entry/spring-webClient사용방법
https://medium.com/@odysseymoon/spring-webclient-사용법-5f92d295edc0
https://happycloud-lee.tistory.com/220
https://godekdls.github.io/Reactive%20Spring/webclient/
https://umbum.dev/1114
https://m.blog.naver.com/spdlqjdudghl/221816497872
https://stackoverflow.com/questions/45418523/spring-5-webclient-using-ssl
https://docs.spring.io/spring-boot/docs/2.1.18.RELEASE/reference/html/boot-features-webclient.html
https://velog.io/@jaepani5015/WebClient
'Backend > Spring+Boot' 카테고리의 다른 글
@RequestPart 사용할 때 주의사항. 그리고 @ModelAttribute (2) | 2022.11.07 |
---|---|
@JsonCreator 주의사항 (0) | 2022.11.02 |
WebClient max connection 그리고 mutate() (0) | 2022.07.22 |
spring-boot-maven-plugin 디버깅 하기 (0) | 2022.05.31 |
WebClient SSL 설정 (0) | 2022.04.19 |