| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |
- Eclipse
- junit
- 인텔리제이
- 테스트자동화
- javascript
- js
- 자바
- CSS
- junit5
- HashMap
- string
- 자바문법
- 자바스크립트
- Visual Studio Code
- 스프링부트
- ArrayList
- 정규식
- SpringBoot
- Java
- math
- 문자열
- IntelliJ
- vscode
- java테스트
- html
- 단위테스트
- 배열
- input
- Array
- list
- Today
- Total
어제 오늘 내일
[Spring WebFlux] 어노테이션 기반 컨트롤러: MVC 개발자라면 10분 만에 적응 가능! 본문
WebFlux를 시작할 때 가장 큰 장벽은 "새로운 문법을 배워야 한다"는 두려움입니다.
하지만 스프링 부트는 기존 MVC 스타일의 코드를 99% 재사용할 수 있게 해 줍니다.
오늘 보여드릴 코드를 보면 "어? 이게 WebFlux라고? MVC랑 똑같은데?"라고 하실 겁니다.
하지만 그 속은 완전히 다른 비동기 엔진(Netty)으로 돌아가고 있죠.
1. 의존성 확인
먼저 spring-boot-starter-web이 아니라 spring-boot-starter-webflux가 필요합니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
// implementation 'org.springframework.boot:spring-boot-starter-web' // 이건 빼세요!
}
이제 톰캣(Tomcat) 대신 네티(Netty) 서버가 뜹니다.
2. 단건 조회: User 대신 Mono<User>
기존 MVC 컨트롤러와 비교해 보겠습니다.
❌ Spring MVC (Blocking)
@RestController
public class MvcController {
@GetMapping("/mvc/users/{id}")
public User getUser(@PathVariable Long id) {
// DB에서 다 가져올 때까지 쓰레드 차단 (3초 대기)
return userService.findById(id);
}
}
✅ Spring WebFlux (Non-Blocking)
@RestController // 똑같음!
public class WebFluxController {
@GetMapping("/webflux/users/{id}")
public Mono<User> getUser(@PathVariable Long id) {
// DB에 요청만 보내고 즉시 리턴 (0.01초)
// 스프링이 나중에 이 Mono를 구독(subscribe)해서 결과를 클라이언트에 줌
return userService.findById(id);
}
}
차이점: 리턴 타입이 User가 아니라 Mono<User>입니다.
이게 답니다. 정말 쉽죠?
3. 목록 조회: List<User> 대신 Flux<User>
여러 건을 조회할 때도 마찬가지입니다.
@GetMapping("/webflux/users")
public Flux<User> getAllUsers() {
// List<User>를 기다리지 않고, 데이터가 흐르는 파이프(Flux)만 리턴
return userService.findAll();
}
브라우저의 반응:
일반적인 JSON 응답(application/json)일 경우, Flux의 모든 데이터가 모일 때까지 기다렸다가 [user1, user2, ...] 형태의 JSON 배열로 한 방에 내려줍니다. (사실상 사용자는 차이를 못 느낍니다.)
WebFlux의 진가를 보려면 "스트리밍"을 해야 합니다.
4. WebFlux의 필살기: 스트리밍 (SSE)
데이터가 10,000개인데, 다 모일 때까지 기다리면 사용자는 답답합니다.
"1개 찾을 때마다 바로바로 화면에 띄워줄 수 없을까?"
이때 사용하는 것이 SSE (Server-Sent Events)입니다.
// MediaType을 꼭 지정해야 함! (text/event-stream)
@GetMapping(value = "/stream/users", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<User> streamUsers() {
// 1초마다 유저 데이터가 하나씩 툭, 툭 전송됨
return userService.findAll()
.delayElements(Duration.ofSeconds(1)); // 시각적 효과를 위한 지연
}
결과:
브라우저나 curl로 요청해 보면, 로딩 바가 도는 게 아니라 1초마다 데이터가 한 줄씩 찍히는 것을 볼 수 있습니다. 이게 바로 Non-Blocking Streaming입니다.
5. 요청 본문 받기 (@RequestBody)
데이터를 받을 때도 Mono나 Flux로 받습니다.
@PostMapping("/users")
public Mono<User> createUser(@RequestBody Mono<UserDto> userDtoMono) {
// 요청 본문(Body)도 비동기로 들어옵니다.
return userDtoMono.flatMap(dto -> userService.save(dto));
}
- 장점: 파일 업로드처럼 큰 데이터가 들어올 때, 메모리에 다 올리지 않고 스트림으로 처리할 수 있어 효율적입니다.
6. 동작 원리: 누가 구독(Subscribe)하나요?
지난 시간에 "구독(subscribe) 안 하면 아무 일도 안 일어난다"고 했죠?
그런데 컨트롤러 코드에는 subscribe()가 없습니다. 누가 하는 걸까요?
바로 스프링 프레임워크(정확히는 Netty와 Reactor Netty Bridge)가 대신해 줍니다.
- 요청이 들어오면 핸들러(컨트롤러)를 찾습니다.
- 컨트롤러가
Mono나Flux를 던져줍니다. (아직 실행 안 됨) - 스프링이 그 리턴 값을 구독(Subscribe)합니다.
- 데이터가 발생(
onNext)할 때마다 HTTP 응답 버퍼에 씁니다. - 완료(
onComplete)되면 연결을 끊습니다.
그래서 우리는 파이프라인 조립(map, flatMap)만 잘해놓고 리턴하면 되는 것입니다.
마치며
오늘의 결론입니다.
- WebFlux 컨트롤러는 MVC와 어노테이션(
@RestController)이 똑같다. - 리턴 타입만
Mono(단건),Flux(다건)로 바꾸면 된다. - SSE(
text/event-stream)를 사용하면 데이터를 실시간으로 흘려보낼 수 있다.
MVC 개발자라면 10분도 안 걸려서 적응하셨을 겁니다.
하지만 WebFlux에는 이것 말고도 "함수형 엔드포인트(Functional Endpoints)"라는 또 다른 스타일이 있습니다. (마치 람다식처럼 라우팅을 정의하는 방식이죠.)
다음 포스팅에서는 "컨트롤러 클래스가 없다고?" 함수형 엔드포인트(Router & Handler) 작성법에 대해 알아보겠습니다.
도움이 되셨다면 좋아요와 댓글 부탁드립니다! 😊
'IT > SpringBoot' 카테고리의 다른 글
| [WebClient] "RestTemplate은 이제 Deprecated!" 비동기 HTTP 요청의 정석, WebClient 사용법 (0) | 2026.04.02 |
|---|---|
| [WebFlux] "컨트롤러가 없다고?" 함수형 엔드포인트(Router & Handler) 작성법 (0) | 2026.04.02 |
| [Reactor] "map과 flatMap, 도대체 뭐가 다른가요?" 마법의 연산자 정복하기 (0) | 2026.04.01 |
| [Reactor] "List랑 뭐가 달라요?" Mono와 Flux, 0개 1개 그리고 N개 (0) | 2026.03.31 |
| [Reactive] "데이터가 강물처럼 흐른다?" 리액티브 스트림즈(Reactive Streams)와 Backpressure 완벽 이해 (0) | 2026.03.31 |
