Mono 와 Flux 는 파이프라인 조립만으로는 실행되어 결과를 반환하지 않는다. 어느 지점에서든 해당 객체들에 subscribe() 를 호출해야 실제 실행이 일어난다.
자세한건 Reactive Streams 를 참고
SpringWebFlux에서는 개발자가 직접 subscribe 를 호출하고 후속처리를 하지 않는게 원칙이다.
// 안티패턴
mono.subscribe(result -> {
// 처리
});
// 그냥 반환
public Mono<Result> handle() {
return service.doSomething(); // subscribe는 프레임워크가 함
}
기본적으로 WebFlux가 알아서 구독하기 때문에 직접 호출하면 프레임워크의 스케쥴링/에러처리 흐름에서 벗어남.
어떻게 WebFlux는 자동으로 Mono나 Flux를 구독할까? WebFlux의 핵심 엔진은 Netty임. 이 기반의 이벤트 루프로 동작하고, HTTP요청이 들어오면 맞는 Handler를 찾아서 실행한뒤, 핸들러가 반환한 Mono/Flux를 직접 구독하는 것 까지 처리해준다.
WebFlux패턴에서 핵심은 조립과 실행이 나뉘어 있다는 것임. 개발자는 조립만 하고 프레임워크는 실행한다.
subscribe 를 직접 호출해도 되는 경우는 반환값이 없는 케이스들이다. fire-and-forget 타입
// fire-and-forget
public Mono<Void> process(String id) {
return mainService.process(id)
.doOnSuccess(r ->
sideEffect.run(r)
.subscribeOn(Schedulers.boundedElastic())
.subscribe(
null,
err -> log.error("사이드 이펙트 실패", err)
)
);
}
Mono기준 오버로딩
subscribe 에 전달되는 람다들은 모두 스트림이 이미 종료된 후에 실행될 콜백들이다. 이들은 데이터스트림안에 있는 데이터에 접근 불가함.
// 1. 아무것도 안 넘김
mono.subscribe();
// 2. 성공 값만
mono.subscribe(
value -> log.info("성공: {}", value)
);
// 3. 성공 + 에러
mono.subscribe(
value -> log.info("성공: {}", value),
error -> log.error("실패", error)
);
// 4. 성공 + 에러 + 완료
mono.subscribe(
value -> log.info("성공: {}", value),
error -> log.error("실패", error),
() -> log.info("완료")
);
// 5. 성공 + 에러 + 완료 + 구독 시점
mono.subscribe(
value -> log.info("성공: {}", value),
error -> log.error("실패", error),
() -> log.info("완료"),
subscription -> subscription.request(1) // 백프레셔 제어
);
인자로 넘긴 람다의 호출 시점
subscribe() 호출
↓
onSubscribe → 구독 시작 시점 (Subscription 받음)
↓
onNext → 값이 emit될 때마다 (Mono는 최대 1번)
↓
onComplete → 정상 완료 시
또는
onError → 에러 발생 시