브라우저의 한 요청은 결국 "어떤 핸들러가 이 요청을 처리하나(매핑) → 그 핸들러를 어떻게 호출하나(어댑트) → 호출 결과를 어떻게 응답으로 쓰나(결과 처리)"라는 세 단계를 거친다. WebFlux는 이 세 단계를 각각 HandlerMapping, HandlerAdapter, HandlerResultHandler 라는 인터페이스로 분리하고, 이들을 조율하는 단일 프런트 컨트롤러 **DispatcherHandler**를 둔다.
Spring MVC의 DispatcherServlet과 구조가 같지만 결정적 차이가 있다. 모든 단계의 반환값이 Mono/Flux이며, 전 과정이 하나의 리액티브 파이프라인으로 합성되어 구독 시점에 비로소 실행된다는 점이다. 스레드를 점유한 채 블로킹 대기하지 않는다.
이 장은 그 골격(org.springframework.web.reactive 최상위 패키지)을 다룬다.
┌──────────────────────────────────────────┐
│ DispatcherHandler │
│ (implements WebHandler) │
│ │
ServerWebExchange │ List<HandlerMapping> 매핑 │
───────────────▶ │ List<HandlerAdapter> 호출 │
handle(exchange) │ List<HandlerResultHandler> 결과 처리 │
: Mono<Void> └───────┬───────────┬───────────┬───────────┘
│ │ │
┌───────────▼┐ ┌───────▼──────┐ ┌─▼────────────────────┐
│HandlerMapping│ │HandlerAdapter│ │ HandlerResultHandler │
│ getHandler() │ │ supports() │ │ supports() │
│ →Mono<Object>│ │ handle() │ │ handleResult() │
└──────────────┘ │→Mono<Handler │ │ →Mono<Void> │
│ Result> │ └──────────────────────┘
└──────┬───────┘
│ 만든다
┌──────▼───────┐
│ HandlerResult │ handler+returnValue
│ │ +returnType+예외핸들러
└──────────────┘
DispatcherHandler:WebHandler구현. 컨테이너 어댑터(WebHttpHandlerBuilder)가 만든WebFilter체인 끝에 위치하는 중앙 디스패처다."webHandler"라는 빈 이름으로 등록되면 자동 발견된다.HandlerMapping: 요청 → 핸들러 객체 매핑. 반환은Mono<Object>(핸들러가@Controller메서드일 수도,HandlerFunction일 수도,WebSocketHandler일 수도 있어 타입이Object).HandlerAdapter: "이 타입의 핸들러를 어떻게 호출하는가"를 추상화.supports(handler)로 자기가 다룰 핸들러인지 판단하고,handle()로 실제 호출해Mono<HandlerResult>를 만든다.HandlerResultHandler:HandlerResult(반환값+타입)를 보고 응답으로 직렬화.@ResponseBodyJSON,ResponseEntity, 뷰 렌더링 등 결과 종류마다 구현이 다르다.HandlerResult: 위 어댑터가 만든 결과 캡슐. 핸들러, 반환값, 반환 타입(ResolvableType),BindingContext, 그리고 비동기/렌더링 단계에서 터진 예외를 처리할DispatchExceptionHandler를 담는다.
DispatcherHandler.handle(exchange)는 전체 처리를 하나의 Reactor 체인으로 조립한다. 핵심 코드는 다음과 같다.
return Flux.fromIterable(this.handlerMappings)
.concatMap(mapping -> mapping.getHandler(exchange)) // ① 첫 매칭 매핑
.next()
.switchIfEmpty(createNotFoundError()) // ② 매칭 없음 → 404
.onErrorResume(ex -> handleResultMono(exchange, Mono.error(ex)))
.flatMap(handler -> handleRequestWith(exchange, handler)); // ③ 어댑트+결과 요청
│
▼
[①] handlerMappings 를 순서대로 concatMap
│ 각 매핑의 getHandler() 를 차례로 시도, 첫 비어있지-않은 결과 채택(next)
▼
핸들러 있음? ──No──▶ [②] switchIfEmpty → ResponseStatusException(404)
│ Yes │
▼ ▼
[③] handleRequestWith(exchange, handler) onErrorResume → handleResultMono
│ handlerAdapters 중 supports(handler) 인 첫 어댑터
▼
adapter.handle() → Mono<HandlerResult>
│
▼
handleResultMono(exchange, resultMono)
│ · DispatchExceptionHandler 인 어댑터들로 onError 보강
│ · result 의 예외핸들러(setExceptionHandler) 로 렌더링 예외 처리
▼
handleResult: resultHandlers 중 supports(result) 인 첫 핸들러
│
▼
resultHandler.handleResult() → Mono<Void> (응답 바디 write)
세 컬렉션(handlerMappings, handlerAdapters, resultHandlers)은 initStrategies()에서 애플리케이션 컨텍스트의 빈을 타입으로 수집하고 AnnotationAwareOrderComparator로 정렬해 둔다. 즉 새 매핑/어댑터/결과처리기를 빈으로 추가하면 디스패처가 자동으로 인지한다 — 핵심 확장점이다.
DispatcherHandler.handle()— 위 파이프라인을 만든다. 주목할 점은Mono<Void>를 반환만 한다는 것. 실제 실행은 상위WebFilter체인이 구독할 때 일어난다. 사전요청(CORS preflight)이면handlePreFlight()로 분기한다.handleRequestWith()— 어댑터 선택. CORS 거부로 응답이 이미FORBIDDEN이면 빈 Mono로 즉시 종료한다. 맞는 어댑터가 없으면IllegalStateException.handleResultMono()— 예외 처리의 이중 안전망. ① 핸들러 선택 전 에러는DispatchExceptionHandler(예:RequestMappingHandlerAdapter가 구현)로 잡아@ExceptionHandler를 태운다. ② 핸들러 호출 후 비동기 반환값이나 렌더링에서 터진 에러는HandlerResult.getExceptionHandler()로 잡는다.handleResult()—resultHandlers를 순회하며supports()가 참인 첫 핸들러에 위임하고.checkpoint(description)로 Reactor 디버그 정보를 남긴다.
HandlerAdapter가 DispatchExceptionHandler를 같이 구현할 수 있다는 설계는 의미가 깊다. Reactive Streams 용어로 handle()은 onNext(정상 매핑된 핸들러 호출), handleError()는 onError(매핑 이전 단계의 에러) 를 담당해, 같은 예외 처리 로직(@ExceptionHandler)을 두 경로 모두에 일관되게 적용한다.
- 전략 패턴 × 3 + 자동 발견: 세 계약 모두 "빈으로 등록 → 컨텍스트에서 수집 → 정렬"이라 OCP를 만족한다.
@EnableWebFlux는 이 빈들의 기본 세트를 채워 넣을 뿐이다(06장). - 핸들러 타입 다형성:
HandlerMapping이Object를 반환하므로 한 디스패처가 애너테이션 컨트롤러(HandlerMethod), 함수형 라우트(HandlerFunction), 정적 리소스(ResourceWebHandler), WebSocket(WebSocketHandler)을 동시에 수용한다. 각각에 대응하는HandlerAdapter가 있을 뿐이다. AbstractHandlerMapping: 모든 URL 기반 매핑의 공통 기반.PathPatternParser, CORS 처리(CorsProcessor), API 버전 전략(ApiVersionStrategy),order를 한곳에서 제공한다. 구체 매핑은getHandlerInternal()만 구현한다.- 확장 훅:
DispatcherHandler를 상속해initStrategies()를 오버라이드하거나, 단순히 커스텀HandlerResultHandler빈을 추가하면 새 반환 타입을 지원하게 만들 수 있다.
WebFlux의 심장은 DispatcherHandler 하나와 그것이 조율하는 세 전략(HandlerMapping/HandlerAdapter/HandlerResultHandler)이다. 한 요청은 "매핑 → 어댑트 → 결과 처리"가 끊김 없이 합성된 단일 Mono<Void>가 되며, 스레드를 붙잡지 않고 구독 시점에 흐른다. 세 전략은 모두 빈으로 자동 수집·정렬되므로, 새로운 핸들러 종류나 반환 타입을 추가하는 일이 "빈 하나 더 등록"으로 끝난다. 다음 장부터는 가장 흔한 핸들러 종류인 애너테이션 컨트롤러가 이 골격 위에서 어떻게 구현되는지 본다.