1. Spring MVC vs WebFlux — 큰 그림
공통점
- 둘 다 Spring Framework로 웹 애플리케이션을 만들 때 사용함.
- 둘 다 HTTP 요청을 받고, 응답을 보냄.
- 둘 다 Controller 또는 비슷한 구조로 라우팅 처리 가능.
MVC란?
- Model-View-Controller 패턴을 따름.
- 요청을 받으면 쓰레드 하나가 요청 전담해서 처리함.
흐름
- 사용자가 브라우저로 요청을 보냄
- DispatcherServlet이 요청을 받음
- @Controller에서 처리
- 데이터를 가공해서 응답 반환
예시
@RestController
class HelloController {
@GetMapping("/hello")
fun hello(): String {
return "Hello, MVC"
}
}
WebFlux란?
Spring WebFlux
- Reactive Programming 기반으로 동작
- 내부적으로 Netty 같은 논블로킹 서버 사용
- 하나의 쓰레드가 여러 요청을 동시에 처리할 수 있음
- 코루틴, 리액터(Flux, Mono) 등을 사용함
흐름
- 사용자가 요청 보냄
- WebFlux가 논블로킹 방식으로 처리
- 비동기적으로 데이터 처리
- 결과가 준비되면 응답
예시 (코루틴 기반)
@RestController
class HelloHandler {
@GetMapping("/hello")
suspend fun hello(): String {
delay(1000) // 논블로킹 지연
return "Hello, WebFlux"
}
}
그럼 "Reactive"란?
Reactive = 반응형
- 시스템이 이벤트에 반응해서 동작하는 것
- 예: 요청이 들어오면 바로 처리하지 않고, 데이터가 준비되면 반응해서 응답함
- 대표 키워드:
- 비동기 (Asynchronous)
- 논블로킹 (Non-blocking)
- 이벤트 스트림 (Event stream)
MVC vs WebFlux 비교표
| Spring MVC | Spring WebFlux | |
| 프로그래밍 모델 | 동기(블로킹) | 비동기(논블로킹, 리액티브) |
| 쓰레드 사용 | 요청마다 하나씩 | 하나의 쓰레드로 여러 요청 처리 |
| 기본 서버 | Tomcat, Jetty | Netty, Undertow |
| 응답 처리 | 즉시 응답 (동기) | 데이터 준비되면 응답 (비동기) |
| 개발 난이도 | 낮음 (단순함) | 높음 (추상화 많음) |
요약
- Spring MVC: 쓰레드 하나가 요청 하나를 처리 (전통적, 쉬움)
- Spring WebFlux: 쓰레드 하나가 여러 요청을 처리 (리액티브, 효율적)
- WebFlux는 서버 리소스를 효율적으로 쓰고, 고성능이 필요한 곳에 적합
Reactive Programming은 실제로 어떤 식으로 동작하나?
MVC 방식과 WebFlux 리액티브 방식의 차이를 코드로 비교해서 이해해보자.
클라이언트가 /hello API를 호출했을 때, 서버가 3초 후에 "Hello"를 응답하도록 처리
1. Spring MVC 방식 (동기 / 블로킹)
@RestController
class MvcController {
@GetMapping("/hello-mvc")
fun hello(): String {
Thread.sleep(3000) // 요청이 들어오면, 3초 동안 현재 쓰레드를 멈춤 (블로킹)
return "Hello from MVC"
}
}
- 요청이 들어오면 서버 쓰레드 하나가 sleep(3초) 함
- 그 동안은 다른 요청을 처리할 수 없음
- 1000명의 사용자가 동시에 요청하면 1000개의 쓰레드가 필요함 → 서버 과부하
2. WebFlux 방식 (논블로킹 / 리액티브)
@RestController
class WebFluxController {
@GetMapping("/hello-flux")
suspend fun hello(): String {
delay(3000) // 3초 대기하지만 쓰레드를 점유하지 않음 (논블로킹)
return "Hello from WebFlux"
}
}
- delay(3000)는 코루틴이 잠깐 멈추는 것처럼 보이지만, 쓰레드를 점유하지 않음
- 같은 쓰레드로 여러 요청을 처리 가능
- 1000명의 요청도 몇 개 쓰레드로 처리가 가능 → 고성능
| 요청 수 | MVC (블로킹) | WebFlux (논블로킹) |
| 1 | O | O |
| 100 | O | O |
| 1000 | 쓰레드 부족 발생 가능 | 처리 가능 (코루틴 + 논블로킹 덕분) |
delay()는 논블로킹, Thread.sleep()은 블로킹 이런 논블로킹 흐름을 가능하게 해주는 게 바로 Reactive Programming
WebFlux의 핵심 구조인 RouterFunction, ServerRequest, ServerHttpRequest
목표
- WebFlux에서 MVC의 @RestController 없이 API를 만드는 방법 이해
- 그 안에서 ServerHttpRequest가 왜 등장하는지 알기
WebFlux의 함수형 스타일 구성
구성 요소 역할 설명
| RouterFunction | 요청 경로(URI)를 핸들러 함수에 연결 |
| HandlerFunction | 실제 요청 처리 (Controller 대신) |
| ServerRequest | 요청 정보 (body, query, header 등) 추상화 |
| ServerHttpRequest | 아주 하위 레벨의 HTTP 요청 정보 |
코드 예제 — "Hello WebFlux" API 만들기
1. RouterFunction
@Configuration
class HelloRouter {
@Bean
fun route(helloHandler: HelloHandler): RouterFunction<ServerResponse> {
return coRouter {
GET("/hello", helloHandler::handleHello)
}
}
}
2. HandlerFunction
@Component
class HelloHandler {
suspend fun handleHello(request: ServerRequest): ServerResponse {
val name = request.queryParam("name").orElse("World")
return ServerResponse.ok().bodyValueAndAwait("Hello, $name")
}
}
여기서 ServerRequest는 뭘까?
- queryParam(), bodyToMono(), headers(), pathVariable() 등 다양한 요청 정보에 접근할 수 있게 해줌
- ServerHttpRequest는 내부적으로 ServerRequest가 사용하는 하위 객체
ServerHttpRequest 예제 — 직접 사용해보기
@Component
class HelloHandler {
suspend fun handleHello(request: ServerRequest): ServerResponse {
val httpRequest: ServerHttpRequest = request.exchange().request
val userAgent = httpRequest.headers.getFirst("User-Agent")
val clientIp = httpRequest.remoteAddress?.address?.hostAddress
val message = "User-Agent: $userAgent, IP: $clientIp"
return ServerResponse.ok().bodyValueAndAwait(message)
}
}
왜 굳이 ServerHttpRequest를 쓰는가?
- ServerRequest는 추상화가 많이 되어 있음
- ServerHttpRequest는 헤더, 경로, 쿠키, IP 정보 등 HTTP 수준의 저수준 정보를 직접 다룰 수 있음
다시 정리
객체 설명 언제 쓰는가
| ServerRequest | WebFlux용 추상화된 요청 객체 | 기본적인 query, body, header 처리 |
| ServerHttpRequest | 실제 HTTP 요청 정보 객체 | IP, 전체 URI, 헤더 직접 접근이 필요할 때 |
결론
- MVC에서는 @GetMapping으로 끝났지만,
- WebFlux에서는 RouterFunction으로 경로 설정하고, HandlerFunction에서 직접 처리
- 이 구조 덕분에 더 유연한 처리가 가능하지만, 개념적으로 더 어렵게 느껴짐
- ServerHttpRequest는 request.exchange().request를 통해 접근 가능
'┝ framework > ┎ Spring' 카테고리의 다른 글
| webClient와 timeout (3) | 2025.05.30 |
|---|---|
| @ResponseEntity (0) | 2025.05.14 |
| controller에서 파라미터를 받는 방법, @PathVariable과 @RequestBody (0) | 2025.04.22 |
| Map<Key, Interface>를 이용한 스프링 빈 활용 (0) | 2025.04.09 |
| [SpringBoot] profile과 bean (0) | 2025.04.04 |
