Files
spring-boot-examples/docs/webclient.md
2025-04-08 19:56:24 +09:00

7.6 KiB

스프링 부트의 WebClient에 대한 설명

스프링 부트에서 WebClient는 스프링 5에서 도입된 비동기(Asynchronous)리액티브(Reactive) HTTP 클라이언트로, 외부 REST API 호출을 처리하기 위한 현대적인 도구입니다. RestTemplate의 후속으로 설계된 WebClient는 리액티브 스트림(Reactive Streams)을 기반으로 동작하며, 비동기 처리와 높은 동시성을 요구하는 환경에서 뛰어난 성능을 제공합니다. 스프링 부트와 통합 시, 선언적이고 유연한 API로 HTTP 요청을 쉽게 구성할 수 있습니다. 아래에서 설정, 사용 방법, 주요 기능을 설명합니다.


1. WebClient의 기본 개념

  • 비동기/리액티브: 요청을 보내고 응답을 기다리지 않고, 결과를 MonoFlux로 비동기적으로 처리.
  • HTTP 메서드 지원: GET, POST, PUT, DELETE 등 모든 표준 메서드 지원.
  • 리액티브 스트림: Mono (단일 결과), Flux (다중 결과)를 반환하여 데이터 스트리밍 가능.
  • 대체재: RestTemplate을 대체하며, 스프링 5.0 이상에서 권장.

2. 의존성

WebClientspring-boot-starter-webflux에 포함되어 있습니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
  • 참고: WebFlux는 리액티브 웹 애플리케이션 프레임워크로, WebClient를 포함.

3. 기본 설정

WebClient는 스프링 빈으로 등록하거나, 필요 시 커스터마이징하여 사용할 수 있습니다.

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient webClient(WebClient.Builder builder) {
        return builder
            .baseUrl("https://api.example.com")  // 기본 URL 설정
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)  // 기본 헤더
            .clientConnector(new ReactorClientHttpConnector(
                HttpClient.create().responseTimeout(Duration.ofSeconds(10))  // 타임아웃 설정
            ))
            .build();
    }
}
  • WebClient.Builder: 타임아웃, 기본 헤더, 필터 등을 설정.

4. WebClient 사용 예시

(1) GET 요청
@Service
public class UserService {

    private final WebClient webClient;

    public UserService(WebClient webClient) {
        this.webClient = webClient;
    }

    // 단일 사용자 조회 (Mono)
    public Mono<User> getUser(Long id) {
        return webClient.get()
            .uri("/users/{id}", id)
            .retrieve()
            .bodyToMono(User.class);
    }

    // 모든 사용자 조회 (Flux)
    public Flux<User> getAllUsers() {
        return webClient.get()
            .uri("/users")
            .retrieve()
            .bodyToFlux(User.class);
    }
}

@Data
public class User {
    private Long id;
    private String name;
    private int age;
}
  • Mono: 단일 결과 예상 시 사용.
  • Flux: 다중 결과(리스트 또는 스트림) 예상 시 사용.
(2) POST 요청
public Mono<User> createUser(User user) {
    return webClient.post()
        .uri("/users")
        .bodyValue(user)  // 요청 바디 설정
        .retrieve()
        .bodyToMono(User.class);
}
  • bodyValue: 객체를 JSON으로 직렬화해 전송.
(3) PUT 요청
public Mono<Void> updateUser(Long id, User user) {
    return webClient.put()
        .uri("/users/{id}", id)
        .bodyValue(user)
        .retrieve()
        .bodyToMono(Void.class);  // 응답 본문 없음
}
(4) DELETE 요청
public Mono<Void> deleteUser(Long id) {
    return webClient.delete()
        .uri("/users/{id}", id)
        .retrieve()
        .bodyToMono(Void.class);
}
(5) 커스텀 요청
public Mono<User> getUserWithHeaders(Long id) {
    return webClient.get()
        .uri("/users/{id}", id)
        .header("Authorization", "Bearer token123")
        .accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(User.class);
}
  • 헤더/쿼리 파라미터: .header(), .uri(builder -> builder.queryParam("key", "value").build())로 추가.
(6) 컨트롤러에서 사용
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        return userService.getUser(id);
    }

    @GetMapping
    public Flux<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @PostMapping
    public Mono<User> createUser(@RequestBody User user) {
        return userService.createUser(user);
    }
}

5. 주요 메서드 및 빌더 체인

  • 요청 빌더:

    • .get(), .post(), .put(), .delete(): HTTP 메서드 지정.
    • .uri(String uri, Object... uriVariables): URL 설정.
    • .header(String name, String value): 헤더 추가.
    • .bodyValue(Object body): 요청 바디 설정.
    • .accept(MediaType... mediaTypes): 수용 가능한 응답 타입.
  • 응답 처리:

    • .retrieve(): 응답을 간단히 처리 (4xx, 5xx는 예외 발생).
    • .bodyToMono(Class<T> elementClass): 단일 객체로 변환.
    • .bodyToFlux(Class<T> elementClass): 다중 객체 스트림으로 변환.

6. 에러 처리

WebClient는 HTTP 오류를 WebClientResponseException으로抛출합니다. 이를 처리하거나 커스텀 로직을 추가할 수 있습니다.

public Mono<User> getUserWithErrorHandling(Long id) {
    return webClient.get()
        .uri("/users/{id}", id)
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, response -> 
            Mono.error(new RuntimeException("Client Error: " + response.statusCode())))
        .onStatus(HttpStatus::is5xxServerError, response -> 
            Mono.error(new RuntimeException("Server Error: " + response.statusCode())))
        .bodyToMono(User.class)
        .onErrorResume(e -> {
            log.error("Error occurred: {}", e.getMessage());
            return Mono.just(new User(-1L, "Unknown", 0));  // 기본값 반환
        });
}
  • onStatus: 특정 상태 코드에 대한 처리.
  • onErrorResume: 예외 발생 시 대체 로직.

7. 장점과 한계

  • 장점:
    • 비동기/리액티브로 높은 동시성 처리.
    • 스트리밍 데이터 지원 (예: 대용량 데이터).
    • 선언적이고 체인 방식의 API.
  • 한계:
    • 리액티브 프로그래밍에 익숙해야 함 (러닝 커브 존재).
    • 간단한 동기 작업에서는 RestTemplate보다 복잡할 수 있음.

8. RestTemplate과의 비교

  • RestTemplate: 동기식, 간단한 사용, 스프링 5 이전 표준.
  • WebClient: 비동기/리액티브, 대규모 트래픽 및 스트리밍에 적합, 스프링 5 이후 권장.
  • 선택 기준: 비동기와 성능이 중요한 경우 WebClient, 단순 동기 호출이면 RestTemplate.

9. 결론

WebClient는 스프링 부트에서 현대적인 REST API 호출을 위한 강력한 도구로, 비동기 및 리액티브 특성을 활용해 높은 확장성과 성능을 제공합니다. MonoFlux를 통해 유연한 데이터 처리가 가능하며, 에러 처리와 커스터마이징 옵션도 풍부합니다. 위 예시를 참고하면 기본적인 HTTP 요청부터 복잡한 시나리오까지 쉽게 구현할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!