Skip to content

Latest commit

 

History

History
102 lines (83 loc) · 9.02 KB

File metadata and controls

102 lines (83 loc) · 9.02 KB

01. 디스패치 아키텍처와 3대 계약

무엇을 / 왜

브라우저의 한 요청은 결국 "어떤 핸들러가 이 요청을 처리하나(매핑) → 그 핸들러를 어떻게 호출하나(어댑트) → 호출 결과를 어떻게 응답으로 쓰나(결과 처리)"라는 세 단계를 거친다. 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(반환값+타입)를 보고 응답으로 직렬화. @ResponseBody JSON, 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 디버그 정보를 남긴다.

HandlerAdapterDispatchExceptionHandler를 같이 구현할 수 있다는 설계는 의미가 깊다. Reactive Streams 용어로 handle()은 onNext(정상 매핑된 핸들러 호출), handleError()는 onError(매핑 이전 단계의 에러) 를 담당해, 같은 예외 처리 로직(@ExceptionHandler)을 두 경로 모두에 일관되게 적용한다.

설계 포인트 / 확장점

  • 전략 패턴 × 3 + 자동 발견: 세 계약 모두 "빈으로 등록 → 컨텍스트에서 수집 → 정렬"이라 OCP를 만족한다. @EnableWebFlux는 이 빈들의 기본 세트를 채워 넣을 뿐이다(06장).
  • 핸들러 타입 다형성: HandlerMappingObject를 반환하므로 한 디스패처가 애너테이션 컨트롤러(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>가 되며, 스레드를 붙잡지 않고 구독 시점에 흐른다. 세 전략은 모두 빈으로 자동 수집·정렬되므로, 새로운 핸들러 종류나 반환 타입을 추가하는 일이 "빈 하나 더 등록"으로 끝난다. 다음 장부터는 가장 흔한 핸들러 종류인 애너테이션 컨트롤러가 이 골격 위에서 어떻게 구현되는지 본다.