백엔드/Spring

[Spring Boot Webflux] 활용하기

MINJIN's 2024. 1. 15. 10:07

1. 주요 구성요소 확인

  Spring Webflux는 Controller와 Router라는 두 가지 주요 컴포넌트를 제공한다. 

 

1) Controller vs Router

  Controller 방식은 Spring MVC와 유사한 방식으로 HTTP 요청을 처리하는 데 사용되며, 이 컴포넌트는 주로 RESTful API 엔드포인트를 구현하는 데 사용된다. Router 방식은 HTTP 요청을 처리하는 데 사용되는 라우터 및 핸들러 함수를 정의하는 데 사용되며 비동기적으로 실행되기 때문에 스레드가 블로킹 되지 않아 더 높은 처리량을 달성할 수 있다. 결론적으로는 Controller 방식은 동기식 요청처리에 적합하며, Router는 비동기식 요청처리에 적합하다.

분류 Router Controller
설정 방식 Java DSL을 사용하여 라우팅 규칙을 정의한다. Annotation을 사용하여 URL 경로를 정의한다.
요청 매핑 요청을 처리하는 메서드를 선택하기 위해 RequestPredicate를 사용한다. URL 경로에 @RequestMapping 어노테이션을 사용한다.
요청 처리 HandlerFunction 또는 WebFlux.fn.ServerResponse 타입의 객체를 반환한다. @RestController 어노테이션을 사용하여 해당 클래스의 모든 메서드가 HTTP 응답을 반환한다는 것을 나타낸다.
예외 처리 RouterFunction에서 예외 처리를 할 수 있다. @ControllerAdvice 어노테이션을 사용하여 예외 처리 핸들러를 등록할 수 있다.

 

2) Router Function

https ://jstobigdata.com/spring/a-functional-endpoint-in-spring-webflux/

 

  Router Function은 Handler Function을 호출하는 역할을 한다. 이를 통해 HTTP 요청을 받고 적절한 Handler Function으로 라우팅할 수 있다. Router Function은 RouterFunctions 클래스를 사용하여 생성할 수 있으며 다양한 메소드를 사용하여 HTTP 요청에 따라 적잘한 Handler Function으로 라우팅할 수 있다. Router Function을 사용하면 라우팅 규칙을 보다 직관적이고 유연하게 제어할 수 있다.

@Bean
public RouterFunction<ServerResponse> routerFunction(ServiceHandler handler) {
    return RouterFunctions.route(RequestPredicates.GET("/service/{id}"), handler::getService)
            .andRoute(RequestPredicates.GET("/services"), handler::getServices)
            .andRoute(RequestPredicates.POST("/service"), handler::createService)
            .andRoute(RequestPredicates.PUT("/service/{id}"), handler::updateService)
            .andRoute(RequestPredicates.DELETE("/service/{id}"), handler::deleteService);

 

3) Handler Function

  WebFlux에서 HTTP 요청을 처리하기 위해 호출하는 메서드이며 단일 입력과 단일 출력을 가진다.

Mono<ServerResponse> handle(ServerRequest request);

 

2. 환경 구성

1) 개발환경

 

  일반적으로 WebFlux는 RDBMS를 사용하는 것이 아닌 비동기적이고 논 블로킹 방식으로 동작하는 MongoDB와 함께 사용한다. Mybatis와 ORM은 동기적인 블로킹 방식으로 동작하기 WebFlux와 함께 사용하면 전체적인 성능에 악영향을 미칠 수 있다. 

 

2) gradle.build 라이브러리 추가

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux' // Spring Boot Webflux
}

 

3) Handler Function 구성

- Hello 메시지를 반환하는 단일 반환(Mono) 값의 서비스를 구성

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class CodeRouter {

    @Bean
    public RouterFunction<ServerResponse> routerFunction(CodeHandler handler) {
        return RouterFunctions.route(RequestPredicates.GET("/hello"), handler::hello)
                .andRoute(RequestPredicates.POST("/api/v2/hello"), handler::hello);
    }
}

 

4) Router Function 구성

- 구성한 Handler Function 함수의 서비스에 대해 Router를 통해 엔드포인트를 연결

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class CodeRouter {

    @Bean
    public RouterFunction<ServerResponse> route(CodeHandler codeHandler) {

        return RouterFunctions.route(
                RequestPredicates.GET("/api/v2/hello").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), codeHandler::hello);
    }
}

 

3. WebClient 이용 방법

1) WebClient 인스턴스 생성

분류 메서드 설명
인스턴스 생성 create() 지정된 URL을 기반으로 WebClient 인스턴스를 만듭니다.
WebClient client = WebClient.create("<http://localhost:8000>");

@Bean
public WebClient webClient() {
    return WebClient.create("<http://localhost:8080>");
}

 

2). WebClient HTTP 메서드, URI, 전송 데이터를 지정

분류 메서드 설명
HTTP 메소드 get() GET 요청을 정의합니다.
HTTP 메소드 post() POST 요청을 정의합니다.
HTTP 메소드 put() PUT 요청을 정의합니다.
HTTP 메소드 delete() DELETE 요청을 정의합니다.
HTTP 메소드 head() HEAD 요청을 정의합니다.
HTTP 메소드 options() OPTIONS 요청을 정의합니다.
HTTP 메소드 patch() PATCH 요청을 정의합니다.
HTTP 메소드 method() 지정된 HTTP 메소드로 요청을 정의합니다.
URI uri() 요청할 엔드포인트 URL을 지정합니다.
요청 바디 body() 요청 바디에 담을 데이터를 설정합니다.
요청 바디 bodyValue() 요청 바디에 담을 데이터를 설정합니다.
응답 결과 bodyToMono() 응답 결과를 Mono로 래핑합니다.
응답 결과 bodyToFlux() 응답 결과를 Flux로 래핑합니다.
응답 결과 retrieve() 요청 결과를 가져오는 ResponseSpec 인스턴스를 반환합니다.
응답 결과 exchange() 요청 결과를 ClientResponse 인스턴스로 가져옵니다.
응답 결과 exchangeToMono() 요청 결과를 Mono로 래핑합니다.
응답 결과 exchangeToFlux() 요청 결과를 Flux로 래핑합니다.
응답 결과 exchangeToBodilessEntity() 응답 결과를 ResponseEntity<Void>로 래핑합니다.
응답 결과 toEntity() 응답 결과를 ResponseEntity로 래핑합니다.
webClient()
    .post()
    .uri("/api/v1/code/code")
    .bodyValue(codeDto)
    .retrieve();

 

3) WebClient의 응답 결과를 지정

분류 메서드 설명
응답 결과 bodyToMono() 응답 결과를 Mono로 래핑합니다.
응답 결과 bodyToFlux() 응답 결과를 Flux로 래핑합니다.
응답 결과 retrieve() 요청 결과를 가져오는 ResponseSpec 인스턴스를 반환합니다.
응답 결과 exchange() 요청 결과를 ClientResponse 인스턴스로 가져옵니다.
응답 결과 exchangeToMono() 요청 결과를 Mono로 래핑합니다.
응답 결과 exchangeToFlux() 요청 결과를 Flux로 래핑합니다.
응답 결과 exchangeToBodilessEntity() 응답 결과를 ResponseEntity<Void>로 래핑합니다.
응답 결과 toEntity() 응답 결과를 ResponseEntity로 래핑합니다.
동기 메소드 block() Publisher가 갖는 값을 동기적으로 반환하는 메소드입니다. 해당 Publisher가 값을 방출할 때까지 대기한 후 값을 반환합니다.

 

4) 종합 결과

  • 인스턴스 최초 생성
  • 인스턴스를 기반으로 HTTP 메서드의 POST 방식 사용
  • 최종 전달하려는 http://localhost:8000/api/v1/code/codePOSTBody 데이터를 담아 전송
  • 전송 후 반환값으로 CodeDto.class 객체의 값으로 반환받기
WebClient client = WebClient.create("<http://localhost:8000>");
ResponseEntity<ApiResponseWrapper> result = client
                .post()
                .uri("/api/v1/code/code")
                .bodyValue(codeDto)
                .retrieve()
                .toEntity(CodeDto.class)
                .block();
Map<String, Object> resultMap = (Map<String, Object>) result.getBody().getResult();
log.info("resultJsonObj :: " + resultMap);

 

5) Builder 메서드

  Builder 메서드를 사용하면 WebClient 인스턴스를 더욱 유연하게 초기화할 수 있다.

분류 메서드 설명
일반적인 설정 baseUrl(String baseUrl) WebClient 인스턴스의 기본 URL을 설정합니다.
일반적인 설정 codecs(Consumer<CodecConfigurer> configurer) 요청 및 응답 바디에 사용할 코덱을 구성합니다.
일반적인 설정 defaultCookie(String name, String value) 요청에 대한 기본 쿠키 값을 설정합니다.
일반적인 설정 defaultHeader(String headerName, String... headerValues) 요청에 대한 기본 헤더 값을 설정합니다.
일반적인 설정 defaultUriVariables(Map<String, ?> defaultUriVariables) 확장된 URI 템플릿에서 사용할 기본 URI 변수를 설정합니다.
일반적인 설정 defaultUriVariables(Consumer<UriBuilderFactory> builderConsumer) UriBuilderFactory를 사용하여 확장된 URI 템플릿에서 사용할 기본 URI 변수를 설정합니다.
일반적인 설정 exchangeStrategies(ExchangeStrategies strategies) 요청 및 응답 바디에 사용할 교환 전략을 구성합니다.
일반적인 설정 filter(ExchangeFilterFunction filterFunction) ExchangeFilterFunction을 WebClient 인스턴스에 추가합니다.
일반적인 설정 filters(Consumer<List<ExchangeFilterFunction>> filters) 여러 ExchangeFilterFunction을 WebClient 인스턴스에 추가합니다.
요청 설정 accept(MediaType... acceptableMediaTypes) 응답에 대한 허용 가능한 미디어 유형을 설정합니다.
요청 설정 attribute(String name, Object value) 요청에 속성을 추가합니다.
요청 설정 body(BodyInserter<?, ? super ClientHttpRequest> inserter) 요청 바디를 설정합니다.
요청 설정 body(BodyInserter<?, ? super ClientHttpRequest> inserter, Class<?> elementClass) 요청 바디를 설정하고 요소의 클래스를 지정합니다.
요청 설정 cookies(Consumer<MultiValueMap<String, HttpCookie>> cookiesConsumer) 요청에 쿠키를 추가합니다.
요청 설정 header(String headerName, String... headerValues) 요청에 헤더를 추가합니다.
요청 설정 headers(Consumer<HttpHeaders> headersConsumer) 요청에 여러 헤더를 추가합니다.
요청 설정 ifModifiedSince(ZonedDateTime ifModifiedSince) 요청의 "If-Modified-Since" 헤더를 설정합니다.
요청 설정 ifNoneMatch(String ifNoneMatch) 요청의 "If-None-Match" 헤더를 설정합니다.
요청 설정 uri(URI uri) 요청 URI를 설정합니다.
요청 설정 uri(String uri, Object... uriVariables) 포맷된 문자열과 URI 변수를 사용하여 요청 URI를 설정합니다.
HTTP 메소드 get() GET 요청을 설정합니다.
HTTP 메소드 post() POST 요청을 설정합니다.
HTTP 메소드 put() PUT 요청을 설정합니다.
HTTP 메소드 delete() DELETE 요청을 설정합니다.
HTTP 메소드 head() HEAD 요청을 설정합니다.
HTTP 메소드 options() OPTIONS 요청을 설정합니다.
HTTP 메소드 patch() PATCH 요청을 설정합니다.
HTTP 메소드 method() 지정된 HTTP 메소드로 요청을 설정합니다.
WebClient client = WebClient.builder()
                .baseUrl("<http://localhost:8000>")
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer my-token")
                .build();
ResponseEntity<ApiResponseWrapper> result = client
                .post()
                .uri("/api/v1/code/code")
                .bodyValue(codeDto)
                .retrieve()
                .toEntity(CodeDto.class)
                .block();
Map<String, Object> resultMap = (Map<String, Object>) result.getBody().getResult();
log.info("resultJsonObj :: " + resultMap);

 

- 참고자료

https://adjh54.tistory.com/233#2.%20WebClient%20HTTP%20Method%2C%20URI%2C%20%EC%A0%84%EC%86%A1%20%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC%20%EC%A7%80%EC%A0%95%ED%95%A9%EB%8B%88%EB%8B%A4.-1