### 스프링 부트의 WebClient에 대한 설명 스프링 부트에서 `WebClient`는 스프링 5에서 도입된 **비동기(Asynchronous)** 및 **리액티브(Reactive)** HTTP 클라이언트로, 외부 REST API 호출을 처리하기 위한 현대적인 도구입니다. `RestTemplate`의 후속으로 설계된 `WebClient`는 리액티브 스트림(Reactive Streams)을 기반으로 동작하며, 비동기 처리와 높은 동시성을 요구하는 환경에서 뛰어난 성능을 제공합니다. 스프링 부트와 통합 시, 선언적이고 유연한 API로 HTTP 요청을 쉽게 구성할 수 있습니다. 아래에서 설정, 사용 방법, 주요 기능을 설명합니다. --- #### 1. WebClient의 기본 개념 - **비동기/리액티브**: 요청을 보내고 응답을 기다리지 않고, 결과를 `Mono`나 `Flux`로 비동기적으로 처리. - **HTTP 메서드 지원**: GET, POST, PUT, DELETE 등 모든 표준 메서드 지원. - **리액티브 스트림**: `Mono` (단일 결과), `Flux` (다중 결과)를 반환하여 데이터 스트리밍 가능. - **대체재**: `RestTemplate`을 대체하며, 스프링 5.0 이상에서 권장. #### 2. 의존성 `WebClient`는 `spring-boot-starter-webflux`에 포함되어 있습니다. ```xml org.springframework.boot spring-boot-starter-webflux ``` - **참고**: `WebFlux`는 리액티브 웹 애플리케이션 프레임워크로, `WebClient`를 포함. --- #### 3. 기본 설정 `WebClient`는 스프링 빈으로 등록하거나, 필요 시 커스터마이징하여 사용할 수 있습니다. ```java @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 요청 ```java @Service public class UserService { private final WebClient webClient; public UserService(WebClient webClient) { this.webClient = webClient; } // 단일 사용자 조회 (Mono) public Mono getUser(Long id) { return webClient.get() .uri("/users/{id}", id) .retrieve() .bodyToMono(User.class); } // 모든 사용자 조회 (Flux) public Flux 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 요청 ```java public Mono createUser(User user) { return webClient.post() .uri("/users") .bodyValue(user) // 요청 바디 설정 .retrieve() .bodyToMono(User.class); } ``` - **`bodyValue`**: 객체를 JSON으로 직렬화해 전송. ##### (3) PUT 요청 ```java public Mono updateUser(Long id, User user) { return webClient.put() .uri("/users/{id}", id) .bodyValue(user) .retrieve() .bodyToMono(Void.class); // 응답 본문 없음 } ``` ##### (4) DELETE 요청 ```java public Mono deleteUser(Long id) { return webClient.delete() .uri("/users/{id}", id) .retrieve() .bodyToMono(Void.class); } ``` ##### (5) 커스텀 요청 ```java public Mono 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) 컨트롤러에서 사용 ```java @RestController @RequestMapping("/api/users") public class UserController { private final UserService userService; public UserController(UserService userService) { this.userService = userService; } @GetMapping("/{id}") public Mono getUser(@PathVariable Long id) { return userService.getUser(id); } @GetMapping public Flux getAllUsers() { return userService.getAllUsers(); } @PostMapping public Mono 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 elementClass)`: 단일 객체로 변환. - `.bodyToFlux(Class elementClass)`: 다중 객체 스트림으로 변환. --- #### 6. 에러 처리 `WebClient`는 HTTP 오류를 `WebClientResponseException`으로抛출합니다. 이를 처리하거나 커스텀 로직을 추가할 수 있습니다. ```java public Mono 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 호출을 위한 강력한 도구로, 비동기 및 리액티브 특성을 활용해 높은 확장성과 성능을 제공합니다. `Mono`와 `Flux`를 통해 유연한 데이터 처리가 가능하며, 에러 처리와 커스터마이징 옵션도 풍부합니다. 위 예시를 참고하면 기본적인 HTTP 요청부터 복잡한 시나리오까지 쉽게 구현할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!