WebClient란?
WebClient는 간단히 말하면 http Request를 보내고 Response를 받아오는 일련의 과정을 편리하게 하는 패키지의 인터페이스이다.
원래 같은 역할을 하는 가장 대표적인 기능이 RestTemplate이지만, Spring 5.0부터 maintenance 모드가 되었고, WebClient를 사용하라고 권고하고 있다.

그렇다면 WebClient와 RestTemplate의 공통점과 차이점은 무엇일까? 공통점은 위에서 말한 것처럼, Http Request를 보내고 Response를 받아오는 역할을 하는 것이고, 차이점은 RestTemplate는 block방식이고, WebClient는 non-block방식입니다.
그리고 RestTemplate은 동기 방식을 사용하고, WebClient는 동기와 비동기 방식을 모두 지원합니다.
Block? Non-Block?
쉽게 말해 블락은 요청을 보내놓고 응답이 올 때까지 아무일도 못하는 것이고, non-block은 요청을 보낸 후 응답이 오는지에 상관 없이 다른 일을 진행하다가 요청을 받으면 그때 처리하는 것입니다. non-block 방식을 사용하게 되면, 동시 접속자 수가 1000명 이상으로 많아졌을 때 block에 비해 속도가 월등히 높아집니다.
동기? 비동기?
우선 영어로는 synchronous operations, asynchronous operations입니다. 동기는 어떤 요청을 한 후, 요청을 한 쪽에서 응답을 계속 궁금해하는 것이고, 비동기는 요청을 한 후 궁금해하지 않는 것입니다. 조금 더 자세히 설명을 하자면, 동기는 요청을 한 후 응답의 리턴을 기다리거나, 바로 처리를 못했다는 return을 받더라도, 계속해서 처리의 완료여부를 확인하는 것입니다. 반면에 비동기는 작업의 완료 여부를 신경쓰지 않고, 응답을 하는 측에 callback을 전달해서 작업 완료후 callback을 호출하게 하는 것입니다. 이것에 대해 그림으로 직관적 잘표현한 그림이 있어서 가져왔습니다. 이렇게 비동기 방식을 사용하게 되면, 큰 전체 서비스 중 일부가 실패해도, 전체에 미치는 영향을 줄이고, 비교적 쉽게 대처할 수 있습니다.

WebClient 사용방법
Dependencies 추가
maven -> pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
gradle -> build.gradle
dependencies { compile 'org.springframework.boot:spring-boot-starter-webflux' }
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
}
참고로 'compile' 명령어는 depreciated되어서, implementationdmf
Instance 생성
인스턴스를 생성하는 방법은 세 가지가 있습니다.
WebClient webClient = WebClient.create();
WebClient client = WebClient.create("http://localhost:8080"); //base Url을 할당하여 생성
//모든 속성을 직접 설정하여 생성
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();
HTTP method 설정
두 가지 방법이 있습니다.
UriSpec<RequestBodySpec> uriSpec = webClient.method(HttpMethod.POST);
UriSpec<ReqeustBodySpec> uriSpec = webClient.post();
URI 설정
//1번째 방법
ReqeustBodySpec bodySpec = uriSpec.uri("/resource");
//2번째 방법(UriBuilder 활용하기)
ReqeustBodySpec bodySpec = uriSpec.uri(uriBuilder -> uriBuilder.pathSegment("/resource").build());
//3번째 방법(java.net.URL 클래스 활용하기)
RequestBodySpec bodySpec = uriSpec.uri(URI.create("/resource"));
Body 설정
//1번째 방법(가장 대표적인 방법)
RequestHeadersSpec<?> headersSpec = bodySpec.bodyValue("바디 내용");
//2번째 방법(Pubisher 활용하기)
RequestHeadersSpec<?> headersSpec = bodySpec.body(Mono.just(new 인스턴스 생성), 클래스.class);
//3번째 방법(BodyInserters활용하기)
RequsestHeadersSpec<?> headersSpec = bodySpec.body(BodyInserters.fromValue("바디 내용"));
//또는
RequsestHeadersSpec<?> headersSpec = bodySpec.body(BodyInserters.fromPublisher(Mono.just("바디 내용")), String.class);
//4번째 방법(MultiValueMap 활용하기)
LinkedMultiValueMap map = new LinkedMultiValueMap();
map.add("key1", "value1");
map.add("key2", "value2");
RequsetHeadersSpec<?> headersSpec = bodySpec.body(BodyInserters.fromMultipartData(map));
Header 설정
ResponseSpec responseSpec = headersSpec.header(
HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.acceptCharset(StandardCharsets.UTF_8)
.ifNoneMatch("*")
.ifModifiedSince(ZonedDateTime.now())
.retrieve();
Response 받기
이제 지금까지 준비한 request를 보내고 reponse를 받는 단계입니다.
이 단계에서는 세 가지 메서드를 이용할 수 있습니다.
exchangeToMono / exchangeToFlux / retrieve
앞의 두 exchange는 모든 http Reponse를 가져와서 핸들링하고 싶을 때 사용하고,
retreive는 간단히 body만 가져와서 디코딩할 때 사용합니다.
그리고 exchangeToMono는 반환 값이 1개 이하일 때, exchangeToFlux는 반환 값이 N개 일 때 사용합니다.
Mono<String> response = headersSpec.exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
return response.bodyToMono(String.class);
} else if (response.statusCode().is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
});
Mono<String> response = headersSpec.retrieve()
.bodyToMono(String.class);
Response를 스트림으로 변경하기
Flux 또는 Mono는 Spring WebFlux에서 사용하는 클래스이기 때문에, Spring MVC를 사용하고 있다면 이 리턴 값을 객체나 스트림으로 변환할 필요가 있습니다.
이를 위해서는 아래와 같이 사용할 수 있습니다.
//Flux인 경우
List<SomeData> results =
webClient.mutate()
.baseUrl("https://some.com/api")
.build()
.get()
.uri("/resource")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(SomeData.class)
.toStream()
.collect(Collectors.toList());
//Mono인 경우
SomeData data =
webClient.mutate()
.baseUrl("https://some.com/api")
.build()
.get()
.uri("/resource/{ID}", id)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(SomeData.class)
.flux()
.toStream()
.findFirst()
.orElse(defaultValue);
참고
https://medium.com/@odysseymoon/spring-webclient-%EC%82%AC%EC%9A%A9%EB%B2%95-5f92d295edc0
https://musma.github.io/2019/04/17/blocking-and-synchronous.html
'Back-end > Spring Boot' 카테고리의 다른 글
| [SpringBoot] HttpSecurity 보안 설정 정리 (0) | 2022.10.13 |
|---|---|
| [SpringBoot] SpringSecurity 인증 처리 흐름 (1) | 2022.09.26 |
| [SpringBoot] 빌더 어노테이션(@Builder)와 빌더 패턴 (0) | 2022.07.22 |
| [SpringBoot] 스프링부트 이해하기 - 6편 리플렉션과 어노테이션으로 디스패처 필터 만들어보기 (0) | 2022.07.21 |
| [SpringBoot] 스프링부트 이해하기 - 5편 Annotation (0) | 2022.07.16 |