Introduction
Spring WebFlux is Spring's response to the rising issue of blocking I/O architecture.
As data is becoming more and more crucial in our age, the approaches we take to retrieve and manipulate it changes. Conventionally, most approaches were "blocking", or rather, synchronous. What this means is that accessing a resource blocked the application from accessing/processing another one until the previous resource was dealt with.
This was perfectly fine with a limited amount of data and resources, though with rising demand for data through high-performance applications - this became a huge problem.
Publishers started overwhelming subscribers and handling resources one-by-one, just like having a single clerk work in the whole supermarket, became too slow for a smooth user experience.
The solution is obvious - have more clerks handle the customers. In terms of software applications, this means a multi-threaded environment and asynchronous, non-blocking calls.
Spring Reactive Stack
Spring's popular servlet stack comprising of Spring MVC uses the conventional methods of accessing and processing data as synchronous calls. With the introduction of Spring 5, Spring's reactive stack was built on top of Reactor Core.
Spring's reactive stack offers additional support for Netty and Servlet 3.1+ containers, giving increased performances for reactive applications:
Credit Spring
Spring WebFlux
Spring WebFlux is a counterpart module for Spring MVC. Where Spring MVC implements synchronous, blocking I/O, Spring WebFlux implements reactive programming via Reactive Streams.
You'll typically use one or the other, though you're also free to combine them together.
Spring WebFlux Dependencies
With a plain Spring Boot project initialized through Spring Initializr, it's enough to add a single dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<version>{version}</version>
</dependency>
The spring-boot-starter-webflux
includes spring-web
, spring-webflux
, spring-boot-starter
, spring-boot-starter-reactor-netty
, etc., so there's no need for any other dependency to be present.
For the applications using Gradle for dependency management, Spring WebFlux can be added in the application's build.gradle
file:
compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux', version: '2.2.2.RELEASE'
Mono and Flux
In Spring WebFlux, the data returned from any operation is packed into a reactive stream. There are two types that embody this approach and are the building blocks in WebFlux applications - Mono
and Flux
.
Mono
is a stream which returns zero items or a single item (0..1
), whereas Flux
is a stream which returns zero or more items (0..N
).
Mono
is therefore used when you're expecting a single (or none) result, such as retrieving a unique user from the database, whereas Flux
is used when you're expecting multiple results or a collection of some sort.
Spring WebFlux Controller
Similar to how we use controllers in classic Spring MVC, for creation of asynchronous REST APIs, we use WebFlux controller. Even the naming conventions are similar to ensure an easy transition between these two approaches.
To mark a class as a controller, we use the @RestController
annotation on a class level.
Having Spring WebFlux and the Reactor Core dependencies in the class path will let Spring know that the @RestController
is in fact a reactive component and add support for Mono
and Flux
.
Spring WebFlux Configuration
As is standard with Spring Boot, we'll handle the configuration through annotations. The @Configuration
and @EnableWebFlux
annotations mark a class as a configuration class and Spring's bean management will register it:
@Configuration
@EnableWebFlux
public class WebFluxConfig {}
To use or extend the existing WebFlux configuration API, you can extend the WebFluxConfigurer
interface:
@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {}
CORS with Spring WebFlux
WebFlux also offers CORS (Cross-Origin Resource Sharing) support, very similar to the Spring MVC servlet stack. The CORS configuration can be set at project level, as well as controller and controller method levels.
To add the CORS configuration at project level you need to @Overrride
the addCorsMappings()
method from the WebFluxConfigurer
interface:
@Configuration
@EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://www.stackabuse.com")
.allowedMethods("GET", "PUT", "DELETE")
.allowedHeaders("testHeader")
.allowCredentials(true);
}
}
And for adding CORS configuration at a more granular level, the @CrossOrigin
annotation is used. This allows for specifying CORS details at controller and method level.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
When @CrossOrigin
is used at the controller class level, all the method level CORS settings inherit from the class level configuration:
@CrossOrigin(origins = "https://www.stackabuse.com")
@RestController
@RequestMapping("/resource")
public class ResourceController {
@GetMapping("/{id}")
public Mono<Resource> getResource(@PathVariable String id) {
}
}
Security with Spring Webflux
Spring also offers the standard security options for its WebFlux framework. You need to add @EnableWebFluxSecurity
at the class level to enable the security configurations in the project:
@EnableWebFluxSecurity
public class WebfluxSecurity {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(
ServerHttpSecurity http) {
http.csrf().disable()
.authorizeExchange()
.pathMatchers(HttpMethod.GET, "/resource/").hasRole("ADMIN")
.pathMatchers("/**").permitAll()
.and()
.httpBasic();
return http.build();
}
}
Spring WebFlux Web Client
Spring WebFlux also includes a reactive web client for managing REST calls. Reactor-Netty is used as default for communicating with the WebFlux server.
The WebFlux client object can be created using either static factory methods or through its builder method (more customization).
The static factory methods are WebClient.create()
and WebClient.create(String baseUrl)
. On the other hand the WebClient.builder
offers following options to add more details to the web client object:
uriBuilderFactory
defaultHeader
defaultCookie
defaultRequest
filter
exchangeStrategies
clientConnector
We'll take a closer look at this in the proceeding section where a demo application is built.
Demo Application
We'll be creating a simple reactive REST API using WebFlux's standard components, which will act as a reactive server. Then, a reactive web client application will be built, which retrieves information from this server and processes the data.
WebFlux Web Server
Repository
Let's start by laying down the foundation for a reactive application by creating a reactive repository interface.
public interface ResourceRepository extends ReactiveCrudRepository<Resource, String> {}
We have extended our repository from WebFlux's ReactiveCrudRepository
, which will return data as Mono
or Flux
datatypes, depending on the number of elements which can be retrieved.
To use MongoDB with Spring WebFlux, we'll add a configuration class which tells Spring that the database should be handled as a reactive component:
@EnableReactiveMongoRepositories
public class MongoDbConfiguration extends AbstractReactiveMongoConfiguration {
@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create();
}
@Override
protected String getDatabaseName() {
return "testDatabase";
}
}
Controller
With our data layer done and configured, let's create a simple REST controller which will retrieve Mono
and Flux
resources through GET requests:
@RestController
@RequestMapping("/resource")
public class ResourceController {
@Autowired
ResourceRepository resourceRepository;
@GetMapping("/{id}")
public Mono<Resource> getResource(@PathVariable String id) {
return resourceRepository.findById(id);
}
@GetMapping
public Flux<Resource> getResources() {
return resourceRepository.findAll();
}
}
Here we are using our reactive ResourceRepository
to find a resource using the id
coming in from the request. Since we're looking for a unique ID, the expected result is either 1 record (the resource with the passed ID is found), or 0 records (there are no records in the database), hence Mono
is used as a return type.
Since findAll()
can return more that one resource element (if present), Flux
is used as the return type.
WebFlux Web Client
Now that we have a basic REST application set up, let's create a Spring WebFlux client which can send requests to the a WebFlux REST application.
To start a web client we need to create a WebClient
object using the server URL:
public WebClient openConnection(String url) {
client = WebClient.create(url);
return client;
}
Once the WebClient
object is created, we can use it to make requests to the web server.
Let's make a request to the server, retrieving a resource with the given ID:
public void getResourceById(String id) {
Mono<Resource> result = client.get()
.uri("/resource/{id}", "1")
.retrieve()
.bodyToMono(Resource.class);
result.subscribe(System.out::println);
}
The bodyToMono()
method is responsible for packing the body of the response into a Mono
.
Similarly, if the calling endpoint returns data as Flux
then we can retrieve it using the following method:
public void getAllResources() {
Flux<Resource> result = client.get()
.uri("/resource")
.retrieve()
.bodyToFlux(Resource.class);
result.subscribe(System.out::println);
}
Conclusion
The Spring Framework allows developers to make reactive, non-blocking applications and APIs using the Spring's WebFlux stack. WebFlux offers annotations very similar to the ones used in classic Spring MVC applications which makes it easier for the developers to transition to reactive code.
In this guide, we've gone over the most important concepts of the WebFlux framework and built a demo application to show them in practice.
The source code can be found on GitHub.