RouterFunction (요청 → 핸들러 라우팅) ≒ @RequestMapping
│
├────── RequestPredicate (매칭 조건 — 메서드·경로·미디어 타입)
└────── HandlerFunction (요청 처리) ≒ 핸들러 메서드 본문
HandlerFunction: ServerRequest를 받아 Mono<ServerResponse>를 반환하는 함수 — @RequestMapping 메서드의 본문에 해당RouterFunction: ServerRequest를 받아 매칭되는 HandlerFunction을 Mono로 반환 — @RequestMapping 애노테이션의 대안
RouterFunction은 매핑 데이터와 동작을 함께 담은 코드RouterFunction<ServerResponse> route = route() // RouterFunctions.route() 빌더
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
GET(패턴, predicate, 핸들러) - HTTP 메서드 + 경로 + 추가 조건(accept)이 모두 맞으면 해당 핸들러로 라우팅handler::getPerson - 메서드 레퍼런스가 곧 HandlerFunctionmethod(), uri(), headers(), queryParam("q"), pathVariable("id") 등 접근자 제공@RequestBody 같은 자동 바인딩이 없으므로 직접 호출| 메서드 | 반환 |
|---|---|
bodyToMono(Person.class) |
Mono<Person> — 단건 역직렬화 |
bodyToFlux(Person.class) |
Flux<Person> — 스트림 역직렬화 |
formData() |
Mono<MultiValueMap<String, String>> — 폼 데이터 |
multipartData() |
Mono<MultiValueMap<String, Part>> — 멀티파트 |
ok(), created(location), notFound(), status(...) → 헤더·contentType 지정 → 본문bodyValue(object) - 준비된 값 하나를 그대로body(publisher, Person.class) - Mono/Flux를 넘기면 프레임워크가 구독·인코딩 (컨트롤러 반환 타입과 같은 원리)build() - 본문 없음 / build(Publisher<Void>) - 완료 신호를 기다렸다가 응답@Controller 역할public class PersonHandler {
private final PersonRepository repository;
public Mono<ServerResponse> getPerson(ServerRequest request) {
int id = Integer.parseInt(request.pathVariable("id")); // 인자 추출도 직접
return repository.getPerson(id)
.flatMap(p -> ok().contentType(APPLICATION_JSON).bodyValue(p))
.switchIfEmpty(ServerResponse.notFound().build()); // 값이 없으면 404
}
public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Person> person = request.bodyToMono(Person.class);
return ok().build(repository.savePerson(person)); // 저장(Mono<Void>) 완료 시 200
}
}
@Valid 자동 적용이 없으므로 Validator를 doOnNext(this::validate)로 직접 적용하고, 실패 시 ServerWebInputException을 던지면 400 응답이 된다RequestPredicates의 조건(GET, path, accept, contentType, queryParam 등)은 and/or로 합성 가능RouterFunction 조합: 빌더의 .add(otherRoute), 또는 and(other) / andRoute(predicate, handler)resources("/resources/**", location) — SPA fallback은 resource(predicate, index)RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1 // 공통 경로 프리픽스 공유
.nest(accept(APPLICATION_JSON), b2 -> b2 // 경로 외 공통 조건도 중첩 가능
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST(handler::createPerson))
.build();
before(request -> ...) - 핸들러 실행 전 요청 가공 (ServerRequest.from(request)로 복사해 수정)after((request, response) -> ...) - 실행 후 응답 가공·로깅filter((request, next) -> ...) - next.handle(request) 호출 여부를 코드가 결정 — 요청을 끊을 수 있어 인증 같은 관심사에 적합
.filter((request, next) -> securityManager.allowAccessTo(request.path())
? next.handle(request) // 통과 → 핸들러 실행
: ServerResponse.status(UNAUTHORIZED).build()) // 차단 → 핸들러 미실행
CorsWebFilter로 처리RouterFunction을 빈으로 등록하면 WebFlux 설정이 DispatcherHandler 흐름에 통합한다
RouterFunctionMapping - 모든 RouterFunction 빈을 감지해 정렬 후 andOther로 결합, 요청을 라우팅HandlerFunctionAdapter - DispatcherHandler가 매핑된 HandlerFunction을 호출하게 하는 어댑터ServerResponseResultHandler - 반환된 ServerResponse의 writeTo를 호출해 응답을 쓴다DispatcherHandler가 매핑 전략만 다르게 가져간다
RouterFunctionMapping은 order-1로RequestMappingHandlerMapping(order0)보다 먼저 검사된다
| 애노테이션 컨트롤러 | 함수형 엔드포인트 | |
|---|---|---|
| 매핑 선언 | @RequestMapping 스캔 |
코드(RouterFunction 빈)로 명시 |
| 라우트 선택 | 가장 구체적 매핑 자동 | 선언 순서 첫 매치 |
| 핸들러 인자 | 리졸버가 자동 바인딩 | ServerRequest에서 직접 추출 |
| 검증 | @Valid 자동 |
Validator 수동 적용 |
| 스타일 | 관례 중심, 간결 | 명시적, 전 과정 제어 |