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

230 lines
7.6 KiB
Markdown

### 스프링 부트의 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
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
```
- **참고**: `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<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 요청
```java
public Mono<User> createUser(User user) {
return webClient.post()
.uri("/users")
.bodyValue(user) // 요청 바디 설정
.retrieve()
.bodyToMono(User.class);
}
```
- **`bodyValue`**: 객체를 JSON으로 직렬화해 전송.
##### (3) PUT 요청
```java
public Mono<Void> updateUser(Long id, User user) {
return webClient.put()
.uri("/users/{id}", id)
.bodyValue(user)
.retrieve()
.bodyToMono(Void.class); // 응답 본문 없음
}
```
##### (4) DELETE 요청
```java
public Mono<Void> deleteUser(Long id) {
return webClient.delete()
.uri("/users/{id}", id)
.retrieve()
.bodyToMono(Void.class);
}
```
##### (5) 커스텀 요청
```java
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) 컨트롤러에서 사용
```java
@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`으로抛출합니다. 이를 처리하거나 커스텀 로직을 추가할 수 있습니다.
```java
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 호출을 위한 강력한 도구로, 비동기 및 리액티브 특성을 활용해 높은 확장성과 성능을 제공합니다. `Mono``Flux`를 통해 유연한 데이터 처리가 가능하며, 에러 처리와 커스터마이징 옵션도 풍부합니다. 위 예시를 참고하면 기본적인 HTTP 요청부터 복잡한 시나리오까지 쉽게 구현할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!