Объединение издателей в Project Reactor

Объединение издателей в Project Reactor

1. обзор

В этой статье мы рассмотрим различные способы комбинированияPublishers вProject Reactor.

2. Maven Зависимости

Давайте настроим наш пример с зависимостямиProject Reactor:


    io.projectreactor
    reactor-core
    3.1.4.RELEASE


    io.projectreactor
    reactor-test
    3.1.4.RELEASE
    test

3. Объединение издателей

Учитывая сценарий, когда нужно работать сFlux<T> илиMono<T>, существуют разные способы объединения потоков.

Давайте создадим несколько примеров, чтобы проиллюстрировать использование статических методов в классеFlux<T>, таких какconcat, concatWith, merge, zip иcombineLatest..

В наших примерах будут использоваться два издателя типаFlux<Integer>, а именноevenNumbers, который являетсяFlux изInteger и содержит последовательность четных чисел, начинающуюся с 1 (min) и ограничивается 5 (переменнаяmax).

Мы создадимoddNumbers, а такжеFlux типаInteger нечетных чисел:

Flux evenNumbers = Flux
  .range(min, max)
  .filter(x -> x % 2 == 0); // i.e. 2, 4

Flux oddNumbers = Flux
  .range(min, max)
  .filter(x -> x % 2 > 0);  // ie. 1, 3, 5

3.1. concat()

Методconcat выполняет конкатенацию входных данных, перенаправляя элементы, отправленные источниками в нисходящем направлении.

Конкатенация достигается путем последовательной подписки на первый источник, затем ожидания его завершения до подписки на следующий и т. Д., Пока не завершится последний источник. Любая ошибка немедленно прерывает последовательность и передается вниз по течению.

Вот быстрый пример:

@Test
public void givenFluxes_whenConcatIsInvoked_thenConcat() {
    Flux fluxOfIntegers = Flux.concat(
      evenNumbers,
      oddNumbers);

    StepVerifier.create(fluxOfIntegers)
      .expectNext(2)
      .expectNext(4)
      .expectNext(1)
      .expectNext(3)
      .expectNext(5)
      .expectComplete()
      .verify();
}

3.2. concatWith ()

Используя статический методconcatWith, мы произведем конкатенацию двух источников типаFlux<T> в результате:

@Test
public void givenFluxes_whenConcatWithIsInvoked_thenConcatWith() {
    Flux fluxOfIntegers = evenNumbers.concatWith(oddNumbers);

    // same stepVerifier as in the concat example above
}

3.3. combineLatest()

Статический метод FluxcombineLatest будет генерировать данные, предоставляемые комбинацией самых последних опубликованных значений из каждого из источников Publisher.

Вот пример использования этого метода с двумя источникамиPublisher иBiFunction в качестве параметров:

@Test
public void givenFluxes_whenCombineLatestIsInvoked_thenCombineLatest() {
    Flux fluxOfIntegers = Flux.combineLatest(
      evenNumbers,
      oddNumbers,
      (a, b) -> a + b);

    StepVerifier.create(fluxOfIntegers)
      .expectNext(5) // 4 + 1
      .expectNext(7) // 4 + 3
      .expectNext(9) // 4 + 5
      .expectComplete()
      .verify();
}

Мы можем видеть здесь, что функцияcombineLatest применила функцию «a + b», используя последний элементevenNumbers (4) и элементыoddNumbers (1,3,5), таким образом создавая последовательность5,7,9.

3.4. merge()с

Функцияmerge выполняет объединение данных из последовательностейPublisher, содержащихся в массиве, в чередующуюся объединенную последовательность:

@Test
public void givenFluxes_whenMergeIsInvoked_thenMerge() {
    Flux fluxOfIntegers = Flux.merge(
      evenNumbers,
      oddNumbers);

    StepVerifier.create(fluxOfIntegers)
      .expectNext(2)
      .expectNext(4)
      .expectNext(1)
      .expectNext(3)
      .expectNext(5)
      .expectComplete()
      .verify();
}

Интересно отметить, что, в отличие отconcat (lazy subscription), источники подписываются охотно.

Здесь мы можем увидеть другой результат функцииmerge, если мы вставим задержку между элементами Publishers:

@Test
public void givenFluxes_whenMergeWithDelayedElementsIsInvoked_thenMergeWithDelayedElements() {
    Flux fluxOfIntegers = Flux.merge(
      evenNumbers.delayElements(Duration.ofMillis(500L)),
      oddNumbers.delayElements(Duration.ofMillis(300L)));

    StepVerifier.create(fluxOfIntegers)
      .expectNext(1)
      .expectNext(2)
      .expectNext(3)
      .expectNext(5)
      .expectNext(4)
      .expectComplete()
      .verify();
}

3.5. mergeSequential()с

МетодmergeSequential объединяет данные из последовательностейPublisher, представленных в массиве, в упорядоченную объединенную последовательность.

В отличие отconcat, на источники охотно подписываются.

Кроме того, в отличие отmerge, их выданные значения объединяются в финальную последовательность в порядке подписки:

@Test
public void testMergeSequential() {
    Flux fluxOfIntegers = Flux.mergeSequential(
      evenNumbers,
      oddNumbers);

    StepVerifier.create(fluxOfIntegers)
      .expectNext(2)
      .expectNext(4)
      .expectNext(1)
      .expectNext(3)
      .expectNext(5)
      .expectComplete()
      .verify();
}

3.6. mergeDelayError()с

mergeDelayError объединяет данные из последовательностей Publisher, содержащихся в массиве, в чередующуюся объединенную последовательность.

В отличие отconcat, на источники охотно подписываются.

Этот вариант статического методаmerge задерживает любую ошибку до тех пор, пока не будет обработана остальная часть невыполненной работы слияния.

Вот примерmergeDelayError:

@Test
public void givenFluxes_whenMergeWithDelayedElementsIsInvoked_thenMergeWithDelayedElements() {
    Flux fluxOfIntegers = Flux.mergeDelayError(1,
      evenNumbers.delayElements(Duration.ofMillis(500L)),
      oddNumbers.delayElements(Duration.ofMillis(300L)));

    StepVerifier.create(fluxOfIntegers)
      .expectNext(1)
      .expectNext(2)
      .expectNext(3)
      .expectNext(5)
      .expectNext(4)
      .expectComplete()
      .verify();
}

3.7. слиться с()

Статический методmergeWith объединяет данные из этогоFlux иPublisher в чередующуюся объединенную последовательность.

Опять же, в отличие отconcat, внутренние источники охотно подписываются на:

@Test
public void givenFluxes_whenMergeWithIsInvoked_thenMergeWith() {
    Flux fluxOfIntegers = evenNumbers.mergeWith(oddNumbers);

    // same StepVerifier as in "3.4. Merge"
    StepVerifier.create(fluxOfIntegers)
      .expectNext(2)
      .expectNext(4)
      .expectNext(1)
      .expectNext(3)
      .expectNext(5)
      .expectComplete()
      .verify();
}

3.8. zip()

Статический методzip агглютинирует несколько источников вместе, то есть ожидает, пока все источники испускают один элемент, и объединяет эти элементы в выходное значение (созданное предоставленной функцией комбинатора).

Оператор будет продолжать делать это до тех пор, пока какой-либо из источников не завершит:

@Test
public void givenFluxes_whenZipIsInvoked_thenZip() {
    Flux fluxOfIntegers = Flux.zip(
      evenNumbers,
      oddNumbers,
      (a, b) -> a + b);

    StepVerifier.create(fluxOfIntegers)
      .expectNext(3) // 2 + 1
      .expectNext(7) // 4 + 3
      .expectComplete()
      .verify();
}

Поскольку отevenNumbers не осталось элемента для создания пары, элемент 5 от издателяoddNumbers игнорируется.

3.9. zipWith()

zipWith выполняет тот же метод, что иzip, но только с двумя издателями:

@Test
public void givenFluxes_whenZipWithIsInvoked_thenZipWith() {
    Flux fluxOfIntegers = evenNumbers
     .zipWith(oddNumbers, (a, b) -> a * b);

    StepVerifier.create(fluxOfIntegers)
      .expectNext(2)  // 2 * 1
      .expectNext(12) // 4 * 3
      .expectComplete()
      .verify();
}

4. Заключение

В этом кратком руководстве мы обнаружили несколько способов объединенияPublishers.

Как всегда, примеры доступны вover on GitHub.