You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
justOrEmpty는 just의 확장 operator로서, just와 달리 emit할 데이터가 null일 경우 NPE이 발생하지 않고 onComplete Signal을 전송한다. 그리고 emit할 데이터가 null이 아닐 경우 해당 데이터를 emit하는 Mono를 생성한다.
publicstaticvoidmain(String[] args) {
Mono
.justOrEmpty(null)
// emit할 데이터가 null이지만 NPE가 발생하지 않고(onError가 아니라) onComplete signal이 발생한다.
.subscribe(data -> {},
error -> {},
() -> log.info("# onComplete"));
}
fromIterable
fromIterable은 iterable에 포함된 데이터를 emit하는 Flux를 생성한다.
just로 데이터가 존재하기 때문에 switchIfEmpty안의 sayDefault 함수가 호출되지 않을 것 같지만 실제로는 호출이 된다. 즉, 불필요하게 호출되는 것이다. 이때 defer로 감싸주면 호출되지 않는다.
using
using은 파라미터로 전달받은 resource를 emit하는 Flux를 생성한다. 첫 번째 파라미터는 읽어 올 resource이고, 두 번째 파라미터는 읽어 온 resource를 emit하는 Flux다. 세 번째 마라미터는 종료 signal(onComplete 또는 onError)이 발생할 경우, resource를 해제하는 등의 후처리를 할 수 있게 해준다.
publicstaticvoidmain(String[] args) {
Pathpath = Paths.get("D:\\resources\\using_example.txt");
Flux
.using(() ->
Files.lines(path), // 파일의 한 라인 씩 읽는다.Flux::fromStream, // stream 형태로 emit한다.Stream::close) // 라인 문자열 데이터 emit이 끝나면 종료 signal 후 처리 -> 이 3 과정을 파일 라인이 끝날때까지 반복한다.
.subscribe(log::info);
}
generate
프로그래밍방식으로 signal 이벤트를 발생시키며 특히 동기적으로 데이터를 하나씩 순차적으로 emit하고자 할 경우 사용된다.
첫 번째 파라미터는 emit할 숫자의 초기값을 지정한다. 두 번째 파라미터는 람다 표현식으로 초기값으로 지정한 숫자부터 emit하고 emit한 숫자를 1씩 증가시켜서 다시 emit하는 작업을 반복하는데 이렇게 1씩 증가하는 숫자를 상태(state) 값으로 정의한다.
publicstaticvoidmain(String[] args) {
Flux
.generate(
// 초기값
() -> 0,
// 람다식 (state, synschronousSink) synschronousSink는 최대 하나의 상태값만 동기적으로 emit하는 인터페이스
(state, sink) -> {
// 상태값을 emitsink.next(state);
// 특정 상태값 도달 시 onCompleteif (state == 10)
sink.complete();
// 상태값 조정return ++state;
})
.subscribe(data -> log.info("# onNext: {}", data));
}
publicstaticvoidmain(String[] args) throwsInterruptedException {
Map<CovidVaccine, Tuple2<CovidVaccine, Integer>> vaccineMap =
getCovidVaccines();
Flux
.fromIterable(SampleData.coronaVaccineNames) // 백신명 emit // innerSequence로 조건에 맞는 데이터인지를 비동기적으로 테스트한 후 결과가 true라면 downstream으로 emit
.filterWhen(vaccine -> Mono
.just(vaccineMap.get(vaccine).getT2() >= 3_000_000)
.publishOn(Schedulers.parallel()))
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(1000);
}
// 출력00:09:10.661 [parallel-2] INFO - # onNext: AstraZeneca00:09:10.693 [parallel-3] INFO - # onNext: Moderna
skip
upstream에서 emit된 데이터 중에서 파라미터로 입력받은 숫자만큼 건너뛴 후, 나머지 데이터를 downstream으로 emit
publicstaticvoidmain(String[] args) throwsInterruptedException {
Flux// interval에서 0부터 1씩 증가한 숫자를 1초 간격으로 emit하는데 skip2이므로 0,1을 skip한다.
.interval(Duration.ofSeconds(1))
.skip(2)// 개수가 아니라 duration을 줄 수도 있다.
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(5500L);
}
take
upstream에서 emit되는 데이터 중에서 파라미터로 입력받은 숫자만큼만 downstream으로 emit한다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
Flux
.interval(Duration.ofSeconds(1))
.take(3) // 숫자가 아니라 duration을 줄 수도 있다.
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(4000L);
}
publicstaticvoidmain(String[] args) {
Flux
.fromIterable(SampleData.btcTopPricesPerYear)
.takeLast(2) // emit된 데이터 중 가장 마지막에 emit된 데이터만 개수만큼 downstream으로 emit한다.
.subscribe(tuple -> log.info("# onNext: {}, {}",
tuple.getT1(), tuple.getT2()));
}
publicstaticvoidmain(String[] args) {
Flux
.fromIterable(SampleData.btcTopPricesPerYear)
// predicate이 treu가 될 때까지 emit된 데이터를 downstream으로 emit한다.// upstream에서 emit된 데이터는 predicate를 평가할 때 사용한 데이터가 포함된다.// Tuples.of(2018, 19_521_543L),// Tuples.of(2019, 15_761_568L),// Tuples.of(2020, 22_439_002L), -> 여기까지 emit된다.// Tuples.of(2021, 63_364_000L)
.takeUntil(tuple -> tuple.getT2() > 20_000_000)
.subscribe(tuple -> log.info("# onNext: {}, {}",
tuple.getT1(), tuple.getT2()));
}
publicstaticvoidmain(String[] args) {
Flux
.fromIterable(SampleData.btcTopPricesPerYear)
// predicate이 true가 되는 동안에만 downstream으로 emit한다.// predicate을 평가할 때 사용한 데이터가 downstream으로 emit되지 않는다.// 평가시 사용된 Tuples.of(2017, 22_483_583L)는 emit되지 않는다.
.takeWhile(tuple -> tuple.getT2() < 20_000_000)
.subscribe(tuple -> log.info("# onNext: {}, {}",
tuple.getT1(), tuple.getT2()));
}
next
upstream에서 emit되는 데이터 중에서 첫 번째 데이터만 downstream으로 전달한다. 만약 empty라면 empty mono를 emit한다.
upstream인 just에서 두 개의 데이터를 emit하면 flatMap 내부의 innser sequence에서 3개의 데이터를 emit한다. upstream 2 * innser sequence 3 = 6개의 데이터가 subscriber에게 전달된다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
Flux
.range(2, 8)
.flatMap(dan -> Flux
.range(1, 9)
// scheduler를 사용해 비동기적으로 진행 // 각 단마다 다른 스레드에서 돈다.// 결과를 보면 2단부터가 아니라 뒤죽박죽인데 flatMap의 inner sequence를 비동기로 동작시키면 순서가 보장되지 않는다는 것을 확인할 수 있다.
.publishOn(Schedulers.parallel())
.map(n -> dan + " * " + n + " = " + dan * n))
.subscribe(log::info);
Thread.sleep(100L);
}
concat
파라미터로 입력되는 publisher의 sequence를 연결해서 데이터를 순차적으로 emit한다. 먼저 입력된 publisher의 sequence가 종료될 때까지 나머지 publisher의 sequence는 subscribe되지 않고 대기하는 특성을 가진다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
Flux
.zip(
Flux.just(1, 2, 3).delayElements(Duration.ofMillis(300L)),
Flux.just(4, 5, 6).delayElements(Duration.ofMillis(500L)),
// 세번째 파라미터로 BiFunction 함수형 인터페이스를 추가해서 emit 데이터 쌍을 변환작업을 거친 후 데이터를 전달할 수 도 있다.
(n1, n2) -> n1 * n2
)
.subscribe(data -> log.info("# onNext: {}", data));
Thread.sleep(2500L);
}
and
Mono의 Complete signal과 파라미터로 입력된 publisher의 complete signal을 결합하여 새로운 Mono를 반환한다. 즉, Mono와 파라미터로 입력된 publisher의 sequence가 모두 종료되었음을 subscriber에게 알릴 수 있다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
restartApplicationServer()
.and(restartDBServer())
.subscribe(
data -> log.info("# onNext: {}", data),
error -> log.error("# onError:", error),
// 최종적으로 후처리 작업에 적합한 operator라고 볼 수 있다.
() -> log.info("# sent an email to Administrator: " +
"All Servers are restarted successfully")
);
Thread.sleep(6000L);
}
privatestaticMono<String> restartApplicationServer() {
returnMono
.just("Application Server was restarted successfully.")
.delayElement(Duration.ofSeconds(2))
.doOnNext(log::info);
}
privatestaticPublisher<String> restartDBServer() {
returnMono
.just("DB Server was restarted successfully.")
.delayElement(Duration.ofSeconds(4))
.doOnNext(log::info);
}
collectList
Flux에서 emit된 데이터를 모아서 List로 변환 후 변환된 List를 emit하는 Mono를 반환한다. 만약 upstream sequence가 비어있다면 비어있는 List를 downstream으로 emit한다.
reactor에서는 upstream publisher에서 emit되는 데이터의 변경 없이 부수 효과만 수행하기 위한 operator들이 있다. doOnXXX로 시작하는 operator들이 그러하다.
doOnXXX로 시작하는 operator는 consumer 또는 runnable 타입의 함수형 인터페이스를 파라미터로 가지기 때문에 별도의 리턴 값이 없다. 따라서 upstream publisher로부터 emiㅅ되는 데이터를 이용해 upstream publisher의 내부 동작을 엿볼 수 있으며 로그를 출력하는 등의 디버깅 용도로 많이 사용된다. 또한 emit과정에서 error가 발생하면 해당 에러에 대한 알림을 전송하는 로직을 추가하는 등 부수 효과를 위한 다양한 로직을 적용할 수 있다.
doOnSubscribe
publisher가 구독 중일 때 트리거되는 동작 추가
doOnRequest
publisher가 요청을 수신할 때 트리거되는 동작 추가
doOnNext
publisher가 데이터를 emit할 때 트리거되는 동작 추가
doOnComplete
publisher가 성공적으로 완료되었을 때 트리거되는 동작 추가
doOnError
publisher가 에러가 발생한 상태로 종료되었을 때 트리거되는 동작을 추가
doOnCancel
publisher가 취소되었을 때 트리거되는 동작 추가
doOnTerminate
publisher가 성공적으로 완료되었을 때 또는 에러가 발생한 상태로 종료되었을 때 트리거되는 동작 추가
doOnEach
publisher가 데이터를 emit할 때, 성공적으로 완료되었을 때, 에러가 발생한 상태로 종료되었을 때 트리거되는 동작 추가
doOnDiscard
updtream에 있는 전체 operator 체인의 동작 중에서 operator에 의해 폐기되는 요소를 조건부로 정리
doAlterTerminate
downstream을 성공적으로 완료한 직후 또는 에러가 발생하여 publisher가 종료된 직후 트리거되는 동작 추가
doFirst
publisher가 구독되기 전에 트리거되는 동작 추가
doFinally
에러를 포함해서 어떤 이유이든 간에 publisher가 종료된 후 트리거되는 동작을 추가
publicstaticvoidmain(String[] args) {
Flux.range(1, 5)
.doFinally(signalType -> log.info("# doFinally 1: {}", signalType))
.doFinally(signalType -> log.info("# doFinally 2: {}", signalType))
.doOnNext(data -> log.info("# range > doOnNext(): {}", data))
.doOnRequest(data -> log.info("# doOnRequest: {}", data))
.doOnSubscribe(subscription -> log.info("# doOnSubscribe 1"))
.doFirst(() -> log.info("# doFirst()"))
.filter(num -> num % 2 == 1)
.doOnNext(data -> log.info("# filter > doOnNext(): {}", data))
.doOnComplete(() -> log.info("# doOnComplete()"))
.subscribe(newBaseSubscriber<>() {
@OverrideprotectedvoidhookOnSubscribe(Subscriptionsubscription) {
request(1);
}
@OverrideprotectedvoidhookOnNext(Integervalue) {
log.info("# hookOnNext: {}", value);
request(1);
}
});
}
// 출력12:52:07.347 [main] INFO - # doFirst()
12:52:07.351 [main] INFO - # doOnSubscribe112:52:07.351 [main] INFO - # doOnRequest: 112:52:07.352 [main] INFO - # range > doOnNext(): 112:52:07.352 [main] INFO - # filter > doOnNext(): 112:52:07.352 [main] INFO - # hookOnNext: 112:52:07.353 [main] INFO - # doOnRequest: 112:52:07.353 [main] INFO - # range > doOnNext(): 212:52:07.353 [main] INFO - # range > doOnNext(): 312:52:07.354 [main] INFO - # filter > doOnNext(): 312:52:07.354 [main] INFO - # hookOnNext: 312:52:07.354 [main] INFO - # doOnRequest: 112:52:07.355 [main] INFO - # range > doOnNext(): 412:52:07.355 [main] INFO - # range > doOnNext(): 512:52:07.355 [main] INFO - # filter > doOnNext(): 512:52:07.355 [main] INFO - # hookOnNext: 512:52:07.355 [main] INFO - # doOnRequest: 112:52:07.356 [main] INFO - # doOnComplete()
12:52:07.356 [main] INFO - # doFinally2: onComplete12:52:07.357 [main] INFO - # doFinally1: onComplete
doFirst가 제일 먼저 동작하고 doFinally는 가장 마지막에 동작하는 것을 볼 수 있다. 구독이 발생하면 doOnSubscribe가 동작하고 subscriber의 요청이 있을 때 doOnRequest가 동작하며 upstream에서 데이터가 emit될 때마다 doOnNext가 동작하는 것을 볼 수 있다. doFinally는 여러 번 호출될 경우 선언한 시점의 역순으로(아래 선언한 것이 우선) 동작한다.
에러 처리를 위한 operator
error
error는 파라미터로 지정된 에러로 종료하는 Flux를 생성한다. 마치 java의 throw 키워드를 사용해서 예외를 의도적으로 던지는 것과 같은 역할을 하며 주로 check exception을 캐치해서 다시 던져야하는 경우 사용한다.
에러 이벤트가 발생했을 때, downstream으로 전파하지 않고 대체 publisher를 리턴한다.
/** * - 예외가 발생했을 때, error 이벤트를 발생시키지 않고, 대체 Publisher로 데이터를 emit하고자 할 경우 * - try ~ catch 문의 경우, catch해서 return default value 하는 것과 같다. */publicstaticvoidmain(String[] args) {
finalStringkeyword = "DDD";
getBooksFromCache(keyword)
.onErrorResume(error -> getBooksFromDatabase(keyword))
.subscribe(data -> log.info("# onNext: {}", data.getBookName()),
error -> log.error("# onError: ", error));
}
publicstaticFlux<Book> getBooksFromCache(finalStringkeyword) {
returnFlux
.fromIterable(SampleData.books)
.filter(book -> book.getBookName().contains(keyword))
.switchIfEmpty(Flux.error(newNoSuchBookException("No such Book")));
}
publicstaticFlux<Book> getBooksFromDatabase(finalStringkeyword) {
List<Book> books = newArrayList<>(SampleData.books);
books.add(newBook("DDD: Domain Driven Design",
"Joy", "ddd-man", 35000, 200));
returnFlux
.fromIterable(books)
.filter(book -> book.getBookName().contains(keyword))
.switchIfEmpty(Flux.error(newNoSuchBookException("No such Book")));
}
privatestaticclassNoSuchBookExceptionextendsRuntimeException {
NoSuchBookException(Stringmessage) {
super(message);
}
}
onErrorContinue
에러가 발생했을 때, 에러 영역 내에 있는 데이터를 제거하고, upstream의 후속 데이터를 emit하는 방식으로 에러를 복구할 수 있는 기능을 제공해준다.
파라미터로 BiConsumer 함수형 인터페이스를 통해 에러 메시지와 에러가 발생했을 때 emit된 데이터를 전달받아서 로그를 기록하는 등의 후처리를 진행할 수 있다.
하지만 공식 문서에는 명확하지 않은 sequence의 동작으로 개발자가 의도하지 않은 상황을 발생시킬 수 있기 때문에 신중한 사용을 권고하며, 대부분의 에러는 doOnError로 로그를 기록하고 onErrorResume등으로 처리할 수 있다고 명시한다.
publicstaticvoidmain(String[] args) {
Flux
.just(1, 2, 4, 0, 6, 12)
.map(num -> 12 / num)
.onErrorContinue((error, num) ->
log.error("error: {}, num: {}", error, num))
.subscribe(data -> log.info("# onNext: {}", data),
error -> log.error("# onError: ", error));
}
// 출력 -> onError는 발생하지 않았고 onErrorContinue 로그만 찍힌 것을 볼 수 있다.13:08:11.142 [main] INFO - # onNext: 1213:08:11.143 [main] INFO - # onNext: 613:08:11.143 [main] INFO - # onNext: 313:08:11.145 [main] ERROR- error: java.lang.ArithmeticException: / byzero, num: 013:08:11.145 [main] INFO - # onNext: 213:08:11.145 [main] INFO - # onNext: 1
retry
retry는 publisher가 데이터를 emit하는 과정에서 에러가 발생하면 파라미터로 입력한 횟수만큼 원본 Flux의 sequence를 다시 구독한다. 만약 파라미터로 Long.Max_Value를 입력하면 재구독을 무한 반복한다.
window는 upstream에서 emit되는 첫 번째 데이터부터 maxSize 숫자만큼의 데이터를 포함하는 새로운 Flux로 분할한다. reactor에서는 이렇게 분할된 flux를 윈도우라고 부른다.
publicstaticvoidmain(String[] args) {
Flux.range(1, 11)
.window(3) // 3개씩 분할된 Flux -> Flux<Flux<Integer>>
.flatMap(flux -> { // flatMap으로 펼치기 -> Flux<Integer> 을 returnlog.info("======================");
returnflux;
})
.subscribe(newBaseSubscriber<>() {
@OverrideprotectedvoidhookOnSubscribe(Subscriptionsubscription) {
subscription.request(2);
}
@OverrideprotectedvoidhookOnNext(Integervalue) {
log.info("# onNext: {}", value);
request(2);
}
});
}
// 출력11:40:55.107 [main] INFO - ======================
11:40:55.112 [main] INFO - # onNext: 111:40:55.113 [main] INFO - # onNext: 211:40:55.113 [main] INFO - # onNext: 311:40:55.113 [main] INFO - ======================
11:40:55.113 [main] INFO - # onNext: 411:40:55.113 [main] INFO - # onNext: 511:40:55.113 [main] INFO - # onNext: 611:40:55.114 [main] INFO - ======================
11:40:55.114 [main] INFO - # onNext: 711:40:55.114 [main] INFO - # onNext: 811:40:55.114 [main] INFO - # onNext: 911:40:55.114 [main] INFO - ======================
11:40:55.114 [main] INFO - # onNext: 1011:40:55.115 [main] INFO - # onNext: 11
buffer
upstream에서 emit되는 첫 번째 데이터부터 maxSize 숫자만큼의 데이터를 List 버퍼로 한 번에 emit한다. 마지막 버퍼에 포함된 데이터 개수는 maxSize보다 적거나 같다. maxSize가 되기 전에 어떤 오류로 인해 들어오지 못하는 상황이 발생할 경우, maxSize가 될때까지 무한정 기다리게 된다. 따라서 bufferTimeout을 사용하는 것이 좋은 선택이다.
upstream에서 emit되는 첫번째 데이터부터 maxSize 숫자만큼의 데이터 또는 maxTime 내에 emit된 데이터를 List 버퍼로 한 번에 emit한다. maxSize나 maxTime 중 먼저 조건에 부합할 때까지 emit된 데이터를 List버퍼로 emit한다.
emit되는 데이터를 keyMapper로 생성한 key를 기준으로 그룹화한 groupedFlux를 리턴하며, 이를 통해 그룹별로 작업을 수행할 수 있다.
publicstaticvoidmain(String[] args) {
Flux.fromIterable(SampleData.books)
// 작가이름 별로 group -> Flux<GroupedFlux<String, Book>>
.groupBy(book -> book.getAuthorName())
.flatMap(groupedFlux ->
groupedFlux
.map(book -> book.getBookName() +
"(" + book.getAuthorName() + ")")
.collectList()
)
.subscribe(bookByAuthor ->
log.info("# book by author: {}", bookByAuthor));
}
// 출력12:02:24.405 [main] INFO - # bookbyauthor: [AdvanceKotlin(Kevin), GettingstartedKotlin(Kevin)]
12:02:24.415 [main] INFO - # bookbyauthor: [AdvanceJavascript(Mike), GettingstartedJavascript(Mike)]
12:02:24.416 [main] INFO - # bookbyauthor: [AdvanceJava(Tom), GettingstartedJava(Tom)]
12:02:24.416 [main] INFO - # bookbyauthor: [AdvancePython(Grace), GettingstartedPython(Grace)]
12:02:24.416 [main] INFO - # bookbyauthor: [AdvanceReactor(Smith), GettingstartedReactor(Smith)]
groupBy의 경우 keyMapper를 통해 생성되는 key를 기준으로 그룹화하면서 valueMapper를 통해 그룹화되는 데이터를 다른 형태로 가공할 수도 있다.
publicstaticvoidmain(String[] args) {
Flux.fromIterable(SampleData.books)
// Flux<GroupedFlux<String, String>>
.groupBy(book ->
book.getAuthorName(), // keyMapperbook -> book.getBookName() + "(" + book.getAuthorName() + ")") // valueMapper // value들을 List로 묶기 -> Flux<List<String>>// flatMap에는 GroupFlux<String, String> 하나씩 들어간다고 보면 된다.
.flatMap(groupedFlux -> groupedFlux.collectList())
.subscribe(bookByAuthor ->
log.info("# book by author: {}", bookByAuthor));
}
// 데이터publicstaticfinalList<Book> books =
Arrays.asList(
newBook("Advance Java", "Tom",
"Tom-boy", 25000, 100),
newBook("Advance Python", "Grace",
"Grace-girl", 22000, 150),
newBook("Advance Reactor", "Smith",
"David-boy", 35000, 200),
newBook("Getting started Java", "Tom",
"Tom-boy", 32000, 230),
newBook("Advance Kotlin", "Kevin",
"Kevin-boy", 32000, 250),
newBook("Advance Javascript", "Mike",
"Tom-boy", 32000, 320),
newBook("Getting started Kotlin", "Kevin",
"Kevin-boy", 32000, 150),
newBook("Getting started Python", "Grace",
"Grace-girl", 32000, 200),
newBook("Getting started Reactor", "Smith",
null, 32000, 250),
newBook("Getting started Javascript", "Mike",
"David-boy", 32000, 330)
);
publicstaticvoidmain(String[] args) {
Flux.fromIterable(SampleData.books)
// 작가 이름을 key로 묶기 -> Flux<GroupedFlux<String, Book>>
.groupBy(book -> book.getAuthorName())
.flatMap(groupedFlux -> // groupedFlux<String, Book> 형태로 하나씩 들어간다.Mono
.just(groupedFlux.key())
// zipWith로 두 개의 Mono를 하나로 합친다.
.zipWith(
groupedFlux// 저자가 얻을 수 있는 인세 계산 -> value로 여러 Book들이 있고, 그것들의 인세 계산된다.
.map(book ->
(int)(book.getPrice() * book.getStockQuantity() * 0.1))
// 인세들을 더하고 BiFunction으로 최종 value 세팅
.reduce((y1, y2) -> y1 + y2),
(authorName, sumRoyalty) ->
authorName + "'s royalty: " + sumRoyalty)
)
.subscribe(log::info);
}
// 출력12:38:04.288 [main] INFO - Kevin's royalty: 1280000
12:38:04.298 [main] INFO - Mike's royalty: 2080000
12:38:04.298 [main] INFO - Tom's royalty: 986000
12:38:04.298 [main] INFO - Grace's royalty: 970000
12:38:04.298 [main] INFO - Smith's royalty: 1500000
다수의 Subscriber에게 Flux를 멀티캐스팅하기 위한 Operator
subscriber가 구독하면 upstream에서 emit된 데이터가 구독 중인 모든 subscriber에게 멀티캐스팅되는데 이를 가능케 해주는 operator들은 cold Sequence를 hot sequence로 동작하게 하는 특징이 있다.
publish
publish operator는 구독을 하더라도 구독 시점에 즉시 데이터를 emit하지 않고, connect를 호출하는 시점에 비로소 데이터를 emit한다. 그리고 hot sequence로 변환되기 때문에 구독 시점 이후에 emit된 데이터만 전달받을 수 있다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
ConnectableFlux<Integer> flux =
Flux
.range(1, 5)
.delayElements(Duration.ofMillis(300L))
.publish(); // 0.3초에 한번씩 emit하는 ConnectableFlux<Integer>을 리턴, 아직 connect 호출이 없어 emit되는 데이터는 없다.Thread.sleep(500L);
flux.subscribe(data -> log.info("# subscriber1: {}", data));
Thread.sleep(200L);
flux.subscribe(data -> log.info("# subscriber2: {}", data));
// connect 호출이 발생하면서 데이터 emit -> 이 시점부터 0.3초에 한번씩 emitflux.connect();
// 이미 emit된 데이터에 대해서는 전달받지 못함 : hot sequenceThread.sleep(1000L);
flux.subscribe(data -> log.info("# subscriber3: {}", data));
Thread.sleep(2000L);
}
// 출력13:16:25.225 [parallel-1] INFO - # subscriber1: 113:16:25.257 [parallel-1] INFO - # subscriber2: 113:16:25.566 [parallel-2] INFO - # subscriber1: 213:16:25.570 [parallel-2] INFO - # subscriber2: 213:16:25.873 [parallel-3] INFO - # subscriber1: 313:16:25.874 [parallel-3] INFO - # subscriber2: 313:16:26.180 [parallel-4] INFO - # subscriber1: 413:16:26.182 [parallel-4] INFO - # subscriber2: 413:16:26.183 [parallel-4] INFO - # subscriber3: 413:16:26.487 [parallel-5] INFO - # subscriber1: 513:16:26.490 [parallel-5] INFO - # subscriber2: 513:16:26.491 [parallel-5] INFO - # subscriber3: 5
autoConnect
publish operator는 구독이 발생하더라도 connect를 직접 호출하기 전까지는 데이터 emit하지 않기 때문에 코드 상에서 connect 호출이 필요하다. 반면 autoConnect는 파라미터로 지정하는 숫자만큼 구독이 발생하는 시점에 upstream 소스에 자동으로 연결되기 때문에 별도의 connect 호출이 필요하지 않다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
Flux<String> publisher =
Flux
.just("Concert part1", "Concert part2", "Concert part3")
.delayElements(Duration.ofMillis(300L))
.publish()
.autoConnect(2);
Thread.sleep(500L);
publisher.subscribe(data -> log.info("# audience 1 is watching {}", data));
Thread.sleep(500L);
publisher.subscribe(data -> log.info("# audience 2 is watching {}", data));
Thread.sleep(500L);
publisher.subscribe(data -> log.info("# audience 3 is watching {}", data));
Thread.sleep(1000L);
}
// 출력13:19:09.507 [parallel-1] INFO - # audience1iswatchingConcertpart113:19:09.529 [parallel-1] INFO - # audience2iswatchingConcertpart113:19:09.836 [parallel-2] INFO - # audience1iswatchingConcertpart213:19:09.839 [parallel-2] INFO - # audience2iswatchingConcertpart213:19:09.840 [parallel-2] INFO - # audience3iswatchingConcertpart213:19:10.150 [parallel-3] INFO - # audience1iswatchingConcertpart313:19:10.152 [parallel-3] INFO - # audience2iswatchingConcertpart313:19:10.153 [parallel-3] INFO - # audience3iswatchingConcertpart3
refCount
파라미터로 입력된 숫자만큼 구독이 발생하는 시점에 upstream소스에 연결되며, 모든 구독이 취소되거나 upstream의 데이터 emit이 종료되면 연결이 해제된다. 이는 주로 무한 스트림 상황에서 모든 구독이 취소될 경우 연결을 해제하는 데 사용된다.
publicstaticvoidmain(String[] args) throwsInterruptedException {
Flux<Long> publisher =
Flux
.interval(Duration.ofMillis(500))
.publish().refCount(1);
// 구독 Disposabledisposable =
publisher.subscribe(data -> log.info("# subscriber 1: {}", data));
Thread.sleep(2100L);
// 구독 취소 -> 모든 구독 취소되었으므로 연결 해제disposable.dispose();
// 구독 발생 -> upstream 소스에 다시 연결 -> upstream은 0부터 다시 emitpublisher.subscribe(data -> log.info("# subscriber 2: {}", data));
Thread.sleep(2500L);
}
// 출력13:24:58.590 [parallel-1] INFO - # subscriber1: 013:24:59.089 [parallel-1] INFO - # subscriber1: 113:24:59.591 [parallel-1] INFO - # subscriber1: 213:25:00.088 [parallel-1] INFO - # subscriber1: 313:25:00.705 [parallel-2] INFO - # subscriber2: 013:25:01.203 [parallel-2] INFO - # subscriber2: 113:25:01.702 [parallel-2] INFO - # subscriber2: 213:25:02.202 [parallel-2] INFO - # subscriber2: 313:25:02.708 [parallel-2] INFO - # subscriber2: 4
만약 autoCommit이었다면 연결이 끊어진 것이 아니기 때문에 다시 구독이 발생하면 0부터가 아니라 이어서 데이터가 emit되었을 것이다.
The text was updated successfully, but these errors were encountered:
operators
Sequence 생성을 위한 operator
justOrEmpty
justOrEmpty는 just의 확장 operator로서, just와 달리 emit할 데이터가 null일 경우 NPE이 발생하지 않고 onComplete Signal을 전송한다. 그리고 emit할 데이터가 null이 아닐 경우 해당 데이터를 emit하는 Mono를 생성한다.
fromIterable
fromIterable은 iterable에 포함된 데이터를 emit하는
Flux
를 생성한다.fromStream
stream에 포함된 데이터를 emit하는 Flux를 생성한다.
range
n부터 1씩 증가한 연속된 수를 m개 emit하는 Flux를 생성한다. for문처럼 특정 횟수만큼 어떤 작업을 처리하고자 할 경우에 주로 사용된다.
defer
operator를 선언한 시점에 데이터를 emit하는 것이 아니라 구독하는 시점에 데이터를 emit하는 Flux 또는 Mono를 생성한다. defer는 데이터 emit을 지연시키기 때문에 꼭 필요한 시점에 데이터를 emit하여 불필요한 프로세스를 줄인다.
just로 데이터가 존재하기 때문에 switchIfEmpty안의 sayDefault 함수가 호출되지 않을 것 같지만 실제로는 호출이 된다. 즉, 불필요하게 호출되는 것이다. 이때 defer로 감싸주면 호출되지 않는다.
using
using은 파라미터로 전달받은 resource를 emit하는 Flux를 생성한다. 첫 번째 파라미터는 읽어 올 resource이고, 두 번째 파라미터는 읽어 온 resource를 emit하는 Flux다. 세 번째 마라미터는 종료 signal(onComplete 또는 onError)이 발생할 경우, resource를 해제하는 등의 후처리를 할 수 있게 해준다.
generate
프로그래밍방식으로 signal 이벤트를 발생시키며 특히 동기적으로 데이터를 하나씩 순차적으로 emit하고자 할 경우 사용된다.
첫 번째 파라미터는 emit할 숫자의 초기값을 지정한다. 두 번째 파라미터는 람다 표현식으로 초기값으로 지정한 숫자부터 emit하고 emit한 숫자를 1씩 증가시켜서 다시 emit하는 작업을 반복하는데 이렇게 1씩 증가하는 숫자를 상태(state) 값으로 정의한다.
create
generate처럼 프로그래밍 방식으로 signal 이벤트를 발생시키나 한번에 여러 건의 데이터를 비동기적으로 emit한다.
create의 경우 subscriber가 request 메서드를 통해 요청을 보내면 publisher가 해당 요청 개수만큼의 데이터를 emit하는 일종의 pull 방식으로 데이터를 처리한다.
그런데 create는 subscriber의 요청과 상관없이 비동기적으로 데이터를 emit하는 push 방식 역시 사용할 수 있다.
subscriber에서 request 요청을 보내는 것과 상관 없이 listener를 통해 들어오는 데이터를 리스닝하고 있다가 실제로 들어오는 데이터가 있을 경우에만 데이터를 emit하는 일종의 push 방식으로 데이터를 처리한다.
create는 한 번에 여러 건의 데이터를 emit할 수 있기 때문에 backpressure 전략이 필요하다.
정리
Sequence 필터링 operator
filter
filter는 upstream에서 emit된 데이터 중에서 조건에 일치하는 데이터만 downstream으로 emit한다.
skip
upstream에서 emit된 데이터 중에서 파라미터로 입력받은 숫자만큼 건너뛴 후, 나머지 데이터를 downstream으로 emit
take
upstream에서 emit되는 데이터 중에서 파라미터로 입력받은 숫자만큼만 downstream으로 emit한다.
next
upstream에서 emit되는 데이터 중에서 첫 번째 데이터만 downstream으로 전달한다. 만약 empty라면 empty mono를 emit한다.
Sequence 변환 operator
map
upstream에서 emit된 데이터를 mapper function을 사용하여 가공한 후 downstream으로 emit한다.
flatMap
upstream에서 emit된 한 건의 데이터는 flatMap의 inner sequence에서 여러 건의 데이터로 변환되고 평탄화 작업을 통해서 하나의 sequence로 병합되어 downstream으로 emit된다.
upstream인 just에서 두 개의 데이터를 emit하면 flatMap 내부의 innser sequence에서 3개의 데이터를 emit한다. upstream 2 * innser sequence 3 = 6개의 데이터가 subscriber에게 전달된다.
concat
파라미터로 입력되는 publisher의 sequence를 연결해서 데이터를 순차적으로 emit한다. 먼저 입력된 publisher의 sequence가 종료될 때까지 나머지 publisher의 sequence는 subscribe되지 않고 대기하는 특성을 가진다.
merge
입력되는 publisher의 sequence에서 emit된 데이터를 인터리빙(interleave)방식으로 병합한다.
merge는 concat처럼 먼저 입력된 publisher의 sequence가 종료될 까지 나머지 publisher의 sequence가 subscribe되지 않고 대기하는 것이 아니라 모든 publisher의 sequence가 즉시 subscribe된다.
zip
zip은 파라미터로 입력되는 publisher sequence에서 emit된 데이터를 결합하는데 각 publisher가 데이터를 하나씩 emit할 때까지 기다렸다가 결합한다.
and
Mono의 Complete signal과 파라미터로 입력된 publisher의 complete signal을 결합하여 새로운 Mono를 반환한다. 즉, Mono와 파라미터로 입력된 publisher의 sequence가 모두 종료되었음을 subscriber에게 알릴 수 있다.
collectList
Flux에서 emit된 데이터를 모아서 List로 변환 후 변환된 List를 emit하는 Mono를 반환한다. 만약 upstream sequence가 비어있다면 비어있는 List를 downstream으로 emit한다.
CollectMap
Flux에서 emit된 데이터를 기반으로 key와 value를 생성하여 Map의 Element로 추가한 후, 최종적으로 Map을 emit하는 Mono를 반환한다. 만약 upstream이 비어있다면 비어있는 Map을 downstream으로 emit한다.
Sequence의 내부 동작 확인을 위한 operator
reactor에서는 upstream publisher에서 emit되는 데이터의 변경 없이 부수 효과만 수행하기 위한 operator들이 있다. doOnXXX로 시작하는 operator들이 그러하다.
doOnXXX로 시작하는 operator는 consumer 또는 runnable 타입의 함수형 인터페이스를 파라미터로 가지기 때문에 별도의 리턴 값이 없다. 따라서 upstream publisher로부터 emiㅅ되는 데이터를 이용해 upstream publisher의 내부 동작을 엿볼 수 있으며 로그를 출력하는 등의 디버깅 용도로 많이 사용된다. 또한 emit과정에서 error가 발생하면 해당 에러에 대한 알림을 전송하는 로직을 추가하는 등 부수 효과를 위한 다양한 로직을 적용할 수 있다.
doFirst가 제일 먼저 동작하고 doFinally는 가장 마지막에 동작하는 것을 볼 수 있다. 구독이 발생하면 doOnSubscribe가 동작하고 subscriber의 요청이 있을 때 doOnRequest가 동작하며 upstream에서 데이터가 emit될 때마다 doOnNext가 동작하는 것을 볼 수 있다. doFinally는 여러 번 호출될 경우 선언한 시점의 역순으로(아래 선언한 것이 우선) 동작한다.
에러 처리를 위한 operator
error
error는 파라미터로 지정된 에러로 종료하는 Flux를 생성한다. 마치 java의 throw 키워드를 사용해서 예외를 의도적으로 던지는 것과 같은 역할을 하며 주로 check exception을 캐치해서 다시 던져야하는 경우 사용한다.
onErrorReturn
onErrorReturn는 에러 이벤트가 발생했을 때, 에러 이벤트를 downstream으로 전파하지 않고 대체 값을 emit한다. java에서 try-catch문의 catch 블록에서 예외에 해당하는 값을 다른 것으로 처리하는 것과 같다고 보면 된다.
onErrorResume
에러 이벤트가 발생했을 때, downstream으로 전파하지 않고 대체 publisher를 리턴한다.
onErrorContinue
에러가 발생했을 때, 에러 영역 내에 있는 데이터를 제거하고, upstream의 후속 데이터를 emit하는 방식으로 에러를 복구할 수 있는 기능을 제공해준다.
파라미터로 BiConsumer 함수형 인터페이스를 통해 에러 메시지와 에러가 발생했을 때 emit된 데이터를 전달받아서 로그를 기록하는 등의 후처리를 진행할 수 있다.
하지만 공식 문서에는 명확하지 않은 sequence의 동작으로 개발자가 의도하지 않은 상황을 발생시킬 수 있기 때문에 신중한 사용을 권고하며, 대부분의 에러는 doOnError로 로그를 기록하고 onErrorResume등으로 처리할 수 있다고 명시한다.
retry
retry는 publisher가 데이터를 emit하는 과정에서 에러가 발생하면 파라미터로 입력한 횟수만큼 원본 Flux의 sequence를 다시 구독한다. 만약 파라미터로 Long.Max_Value를 입력하면 재구독을 무한 반복한다.
Sequence의 동작 시간 측정을 위한 operator
elapsed
emit된 데이터 사이의 경과 시간을 측정해서 tuple<Long, T> 형태로 downstream에 emit한다. emit된 데이터는 onSubscribe Signal과 데이터 사이의 시간을 기준으로 측정한다.
Flux Sequence 분할을 위한 operator
window
window는 upstream에서 emit되는 첫 번째 데이터부터 maxSize 숫자만큼의 데이터를 포함하는 새로운 Flux로 분할한다. reactor에서는 이렇게 분할된 flux를 윈도우라고 부른다.
buffer
upstream에서 emit되는 첫 번째 데이터부터 maxSize 숫자만큼의 데이터를 List 버퍼로 한 번에 emit한다. 마지막 버퍼에 포함된 데이터 개수는 maxSize보다 적거나 같다. maxSize가 되기 전에 어떤 오류로 인해 들어오지 못하는 상황이 발생할 경우, maxSize가 될때까지 무한정 기다리게 된다. 따라서 bufferTimeout을 사용하는 것이 좋은 선택이다.
bufferTimeout
upstream에서 emit되는 첫번째 데이터부터 maxSize 숫자만큼의 데이터 또는 maxTime 내에 emit된 데이터를 List 버퍼로 한 번에 emit한다. maxSize나 maxTime 중 먼저 조건에 부합할 때까지 emit된 데이터를 List버퍼로 emit한다.
groupBy
emit되는 데이터를 keyMapper로 생성한 key를 기준으로 그룹화한 groupedFlux를 리턴하며, 이를 통해 그룹별로 작업을 수행할 수 있다.
groupBy의 경우 keyMapper를 통해 생성되는 key를 기준으로 그룹화하면서 valueMapper를 통해 그룹화되는 데이터를 다른 형태로 가공할 수도 있다.
다수의 Subscriber에게 Flux를 멀티캐스팅하기 위한 Operator
subscriber가 구독하면 upstream에서 emit된 데이터가 구독 중인 모든 subscriber에게 멀티캐스팅되는데 이를 가능케 해주는 operator들은 cold Sequence를 hot sequence로 동작하게 하는 특징이 있다.
publish
publish operator는 구독을 하더라도 구독 시점에 즉시 데이터를 emit하지 않고, connect를 호출하는 시점에 비로소 데이터를 emit한다. 그리고 hot sequence로 변환되기 때문에 구독 시점 이후에 emit된 데이터만 전달받을 수 있다.
autoConnect
publish operator는 구독이 발생하더라도 connect를 직접 호출하기 전까지는 데이터 emit하지 않기 때문에 코드 상에서 connect 호출이 필요하다. 반면 autoConnect는 파라미터로 지정하는 숫자만큼 구독이 발생하는 시점에 upstream 소스에 자동으로 연결되기 때문에 별도의 connect 호출이 필요하지 않다.
refCount
파라미터로 입력된 숫자만큼 구독이 발생하는 시점에 upstream소스에 연결되며, 모든 구독이 취소되거나 upstream의 데이터 emit이 종료되면 연결이 해제된다. 이는 주로 무한 스트림 상황에서 모든 구독이 취소될 경우 연결을 해제하는 데 사용된다.
만약 autoCommit이었다면 연결이 끊어진 것이 아니기 때문에 다시 구독이 발생하면 0부터가 아니라 이어서 데이터가 emit되었을 것이다.
The text was updated successfully, but these errors were encountered: