2025-04-08T19:56:24
This commit is contained in:
230
docs/webclient.md
Normal file
230
docs/webclient.md
Normal file
@@ -0,0 +1,230 @@
|
||||
### 스프링 부트의 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 요청부터 복잡한 시나리오까지 쉽게 구현할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!
|
||||
Reference in New Issue
Block a user