Spring Webflux: Getting started
Excited to dive into Reactive Streams? Take a look at how to use Spring Webflux and Java 9 for asynchronous processing.
Join the DZone community and get the full member experience.
Join For FreeThis post will be about how I got started with Spring Webflux. A few weeks ago, I started to read in detail about Spring Webflux, Reactive Streams, etc. Looking back at my journey, I did things in the wrong order. In this post, I have tried to structure the information in order to give you a plan for getting started with Spring Webflux. The examples can be found on GitHub.
Reactive Manifesto
The basis for Reactive Streams can be found in the Reactive Manifesto. It is advised to read this manifesto, it is only 2 pages long, so it won't take long to read it.
Reactive Streams
Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure, as can be read on the website. This means that a source can send data to a destination without overwhelming the destination with too much data. The destination will tell the source how much data it can handle. This way, resources are used more efficiently. The Reactive Streams specification wants to set a standard also.
The standard for the JVM is available in GitHub. Summarized, the specification consists of the following items:
- Publisher: The publisher is the source that will send the data to one or more subscribers.
- Subscriber: A subscriber will subscribe itself to a publisher, will indicate how much data the publisher may send and will process the data.
- Subscription: On the publisher side, a subscription will be created, which will be shared with a subscriber.
- Processor: A processor can be used to sit between a publisher and a subscriber, this way a transformation of the data can take place.
The Reactive Streams specification is a standard and since Java 9, it is included in Java with the Flow API. Take a closer look at the Reactive Streams specification and the Java 9 Flow API. As you will notice, the interfaces for Publisher, Subscriber, Subscription, and Processor are identical.
Project Reactor
The next step to take in our journey is Project Reactor. This project provides a Reactive library based on the Reactive Streams specification. In other words, an implementation of the specification. Basically, Reactor provides two types:
- Mono: implements Publisher and returns 0 or 1 elements;
- Flux: implements Publisher and returns N elements.
But what is its relation with Spring Webflux? Spring Webflux is based on Project Reactor and this brings us to the main goal of this post: learn more about Spring Webflux.
Spring Webflux
Spring Webflux is introduced with Spring 5, the official Spring documentation can be found here. You can read the whole documentation, but it is quite a lot of information. I suggest to first look at an example (this article) and then refer back to the documentation. Important to know is that there are two ways to use Spring Webflux. One using annotations, which is quite similar to Spring MVC, and one using a functional way. I will use the functional way. Besides that, you must know that Spring MVC and Spring Webflux exist next to each other. Spring MVC is used for synchronous processing, while Spring Webflux for asynchronous processing.
Spring Webflux Getting Started Example
To make it easy on myself, I tried to follow the Spring Webflux getting started example: Building a Reactive RESTful Web Service. I followed the guide and used also Java 9. However, it did not run out-of-the-box. I had to make some adaptations. I will not repeat the steps to follow (you can read it in the guide), but I will document my findings when trying to build and run the application. The example can be found in package com.mydeveloperplanet.myspringwebfluxplanet.greeting.
First, I had to add the Milestones repository to the POM in order to use spring-boot-starter-parent version 2.0.0.RC2.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RC2</version>
</parent>
...
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
As of the 1st of March, the above is not necessary anymore since Spring Boot is now generally available. It is now sufficient to only refer to this GA release:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
Also, in the dependencies, I had to use spring-boot-starter-webflux instead of spring-boot-starter-web.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Because I used Java 9, I had to add the following module-info :
module com.mydeveloperplanet.myspringwebfluxplanet {
requires reactor.core;
requires spring.web;
requires spring.context;
requires spring.webflux;
requires spring.boot;
requires spring.boot.autoconfigure;
}
Now, build and run the example with Maven target spring-boot:run.
In the console, we see that the following line is printed:
>> result = Hello, Spring!
You can also test it in the browser, using the URL http://localhost:8080/hello.
Looking at the console log, we see that the Netty Webserver is being used. This is the default for Spring Webflux applications.
Spring Webflux Basic Example
Now, let's see if we can create a basic example and understand what is exactly going on here.
We create package com.mydeveloperplanet.myspringwebfluxplanet.examples and add an ExampleRouter and an ExampleHandler, similar to the Greeting example but we try to do the minimum in order to make it accessible at URL http://localhost:8080/example.
The ExampleRouter becomes the following:
@Configuration
public class ExampleRouter {
@Bean
public RouterFunction<ServerResponse> route(ExampleHandler exampleHandler) {
return RouterFunctions
.route(RequestPredicates.GET("/example").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::hello);
}
}
The ExampleHandler becomes the following:
@Component
public class ExampleHandler {
public Mono<ServerResponse> hello(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello, Spring Webflux Example!"));
}
}
Execute the Maven target spring-boot:run. Go to the URL http://localhost:8080/example in your browser. The following error is shown:
In the console log, we see the following:
Overriding bean definition for bean 'route' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=exampleRouter; factoryMethodName=route; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mydeveloperplanet/myspringwebfluxplanet/examples/ExampleRouter.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=greetingRouter; factoryMethodName=route; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [com/mydeveloperplanet/myspringwebfluxplanet/greeting/GreetingRouter.class]]
Now change the method route in class ExampleRouter into routeExample and re-run the application. Now the browser shows the following as expected:
Hello, Spring Webflux Example!
As a minimum, we only need a Router and a Handler. We must be sure that the Router Bean has a unique name, otherwise, it may be overwritten by another Bean with the same name. The RouterFunction determines the route it matches and eventually resolves to a Handler method.
Spring Webflux 'one step further' Example
Now we know how to set up the basics, let's take it one step further and explore some extra functionality.
We create a method routeExampleOneStepFurther in class ExampleRouter and chain two routes with each other by means of the andRoute method. This way, we can chain several routes to each other and let them resolve to different handler methods. The routes are evaluated one by one, so you must make sure to define specific routes before more generic routes. The method routeExampleOneStepFurther is the following:
public RouterFunction<ServerResponse> routeExampleOneStepFurther(ExampleHandler exampleHandler) {
return RouterFunctions
.route(RequestPredicates.GET("/exampleFurther1").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::helloFurther1)
.andRoute(RequestPredicates.GET("/exampleFurther2").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::helloFurther2);
}
The handler methods are created in class ExampleHandler and just return a string:
public Mono<ServerResponse> helloFurther1(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello, Spring Webflux Example 1!"));
}
public Mono<ServerResponse> helloFurther2(ServerRequest request) {
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello, Spring Webflux Example 2!"));
}
Now build and run the application and invoke the URL http://localhost:8080/exampleFurther1. The result is as follows:
Hello, Spring Webflux Example 1!
The same way, URL http://localhost:8080/exampleFurther2 shows us the following result:
Hello, Spring Webflux Example 2!
We have tested manually, now let's create a unit test for it. The unit test is the following:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ExampleRouterTest {
@Autowired
private WebTestClient webTestClient;
@Test
public void testExampleOneStepFurther() {
webTestClient
.get().uri("/exampleFurther1")
.accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello, Spring Webflux Example 1!");
webTestClient
.get().uri("/exampleFurther2")
.accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello, Spring Webflux Example 2!");
}
}
The test is based on the Spring Webflux example. During the test, a server will be started on a random port and will invoke the URLs one after each other. The exchange method gives us access to the response. First, the response status is checked and then we check whether the response is correct. In the Spring Webflux example, the equals method is used to verify the response, but this results always in success, even if the response is not equal to the result. This is not correct, the method isEqualTo must be used in order to have a correct test.
Spring Webflux 'last steps' Example
Finally, we will create a route which takes a parameter, add a test for it and add a test for an unsuccessful API call.
In our routeExampleOneStepFurther method, we add another route and let it resolve to the method helloFurther3 of class ExampleHandler. The route is the following:
.andRoute(RequestPredicates.GET("/exampleFurther3").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), exampleHandler::helloFurther3)
The method helloFurther2 extracts a name parameter and says hello to the given name:
public Mono<ServerResponse> helloFurther3(ServerRequest request) {
String name = request.queryParam("name").get();
return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN)
.body(BodyInserters.fromObject("Hello, " + name + "!"));
}
Build and run the application and go to url http://localhost:8080/exampleFurther3?name=My%20Developer%20Planet. This gives us the expected result:
Hello, My Developer Planet!
The test we add is similar to the previous tests we have written:
@Test
public void testExampleWithParameter() {
webTestClient
.get().uri("/exampleFurther3?name=My Developer Planet")
.accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("Hello, My Developer Planet!");
}
To conclude with, we add a test which would return a 404 http response. The expected status to an unresolved url path is tested with the isNotFound method:
@Test
public void testExampleWithError() {
webTestClient
.get().uri("/example/something")
.accept(MediaType.TEXT_PLAIN)
.exchange()
.expectStatus().isNotFound();
}
Summary
In this post I tried to give some information on how to get started with Spring Webflux:
- Suggested reading before getting started with Reactive programming
- A basic Spring Webflux example provided by Spring
- A few basic Spring Webflux examples in order to perform some first steps
It must be clear that this is only very basic information, and from this point on, there is a lot of other things to explore concerning Spring Webflux.
Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments