Cancel an Ongoing Flux in Spring WebFlux
Last Updated :
20 Mar, 2024
Spring WebFlux is a reactive, non-blocking web framework for creating contemporary, scalable Java online applications. It is a component of the Spring Framework and implements Java reactive programming using the Reactor library.
Example of Cancel an Ongoing Flux in Spring WebFlux
Below is the Example of Cancel an Ongoing Flux in Spring WebFlux:
Java
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
public class FluxCancellationExample {
public static void main(String[] args) throws InterruptedException {
// Create a Flux with delayed elements
Flux<Integer> flux = Flux.range(1, 10)
.delayElements(java.time.Duration.ofSeconds(1));
// Subscribe to the Flux and get a Disposable
Disposable disposable = flux.subscribe(
i -> System.out.println("Received: " + i),
e -> System.err.println("Error: " + e.getMessage()),
() -> System.out.println("Flux completed")
);
// Wait for a while
Thread.sleep(5000);
// Dispose the Flux to cancel the ongoing subscription
disposable.dispose();
// After disposal, you won't receive further elements from the Flux
}
}
Implementation to Cancel an Ongoing Flux in Spring WebFlux
Below are the implementation steps to Cancel an Ongoing Flux in Spring WebFlux.
Step 1: Create a Flux
Make an instance of Flux to represent the data stream. As an illustration:
Flux<Integer> flux = Flux.range(1, 10)
.delayElements(Duration.ofSeconds(1));
Step 2: Subscribe to the Flux
Get a Disposable object by subscribing to the Flux. We can terminate the active subscription using this item.
Java
// Creating a disposable to manage the subscription
Disposable disposable =
// Subscribing to the Flux with three lambda expressions:
flux.subscribe(
// Consumer for onNext event - printing the received item
i -> System.out.println("Received: " + i),
// Consumer for onError event - printing the error message
e -> System.err.println("Error: " + e.getMessage()),
// Runnable for onComplete event - printing a completion message
() -> System.out.println("Flux completed")
);
Step 3: Add Maven Dependencies
Consider a very basic scenario where some sensor data is received as a Flux, and we wish to use the different WebFlux options to cancel the data emission based on the subscription. It is now time to list these dependencies in the project.
<dependencies>
<!-- Spring Boot WebFlux Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Project Reactor Spring Integration -->
<dependency>
<groupId>org.projectreactor</groupId>
<artifactId>reactor-spring</artifactId>
<version>${reactor-spring.version}</version>
</dependency>
<!-- Reactor Test for Testing -->
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Step 4: Use dispose() Method
Let’s build up an example where we have an integer flux that emits numbers between 1 and 10 after a 1-second delay. In order to publish the data on the console, we will subscribe to the flux. The thread will then be put to sleep for 5 milliseconds before dispose() is called.
Java
import org.junit.jupiter.api.Test;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
class FluxDisposalTest {
@Test
void giveAnOnGoingFlux_whenDispose_thenCancelsFluxExplicitly() throws InterruptedException {
// Create a flux with delayed elements
Flux<Integer> flux = Flux.range(1, 10)
.delayElements(Duration.ofSeconds(1));
// Counter for received elements
AtomicInteger count = new AtomicInteger(0);
// Subscribe to the flux and handle elements and errors
Disposable disposable = flux.subscribe(
i -> {
System.out.println("Received: " + i);
count.incrementAndGet();
},
e -> System.err.println("Error: " + e.getMessage())
);
// Wait for a while
Thread.sleep(5000);
// Dispose the flux and check if it's disposed
System.out.println("Will Dispose the Flux Next");
disposable.dispose();
if (disposable.isDisposed()) {
System.out.println("Flux Disposed");
}
// Assert the expected count of received elements
assertEquals(4, count.get());
}
}
Step 5: Test the Cancellations of flux
As soon as the flux is cancelled, we will output the relevant signals to make the test simpler. But in practice, this phase may handle any resource cleaning, such as disconnecting a connection, for example. As part of the post-cancellation cleaning, let’s quickly verify that our desired strings are printed when the Flux is cancelled:
Java
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.SignalType;
import java.io.PrintStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
// Class to test Flux cancellation behavior
class FluxCancellationTest {
// Test to verify the cancellation message is printed when the Flux is canceled
@Test
void whenFluxCanceled_thenPrintCancellationMessage() throws InterruptedException {
// Creating a list to store results
List<Integer> result = new ArrayList<>();
// Creating a mock PrintStream
PrintStream mockPrintStream = mock(PrintStream.class);
// Setting the mock PrintStream as System.out
System.setOut(mockPrintStream);
// Creating a Flux emitting sensor data at intervals
Flux<Integer> sensorData = Flux.interval(Duration.ofMillis(200))
// Handling cancellation
.doOnCancel(() -> System.out.println("Flux Canceled"))
// Handling completion or error
.doFinally(signalType ->
System.out.println("Flux Completed because of " +
(signalType == SignalType.CANCEL ? "Cancellation" : "Error or Completion")))
// Mapping the emitted values
.map(i -> ThreadLocalRandom.current().nextInt(1, 2002))
// Storing the emitted values
.doOnNext(result::add);
// Subscribing to the Flux
Disposable subscription = sensorData.subscribe();
// Waiting for some time
Thread.sleep(2000);
// Disposing the subscription
subscription.dispose();
// Capturing printed messages
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
// Verifying that the expected messages were printed
verify(mockPrintStream, times(2)).println(captor.capture());
// Asserting the captured messages
assertThat(captor.getAllValues()).contains("Flux Canceled", "Flux Completed due to Cancellation");
}
}
- The FluxCancellationTest JUnit test case demonstrates how to use Spring WebFlux to terminate an active Flux subscription in a reactive environment.
- In order to conduct the test, a faked PrintStream for recording console output must be set up, and a Flux stream that periodically emits random numbers must be created and subscribed to.
- The test confirms that the console prints the relevant messages upon cancellation of the subscription.
Share your thoughts in the comments
Please Login to comment...