Spring WebFlux Functional Endpoints

By Arvind Rai, September 27, 2020
This page will walk through Spring WebFlux functional endpoints example. Spring WebFlux application can be created using annotated controller and functional web programming (WebFlux.fn). In functional web programming, we create functional endpoints to serve the HTTP requests. The functional programming model in Spring WebFlux is lightweight which uses functions to route and handle the requests. The Spring WebFlux functional programming (WebFlux.fn) model and annotation-based programming model, both run on the same reactive core foundation.
The WebFlux.fn uses HandlerFunction and RouterFunction API. The HandlerFunction is a function that takes ServerRequest and returns delayed ServerResponse. The RouterFunction routes an incoming request to a handler function. The RouterFunction is a function that takes ServerRequest and returns a delayed HandlerFunction.
Spring provides @EnableWebFlux annotation to be added with @Configuration. The @EnableWebFlux enables WebFlux features for annotated controller and functional web programming model. In Spring Boot Application, @EnableWebFlux is not needed because Spring Boot autoconfigures WebFlux features. If we want complete control of WebFlux features in our Spring Boot application then we should use @EnableWebFlux.
On this page we will create Spring Boot WebFlux CRUD application using functional web programming model. By default Spring Boot uses Reactor as reactive library and Netty as server.

1. Technologies Used

Find the technologies being used in our example.
1. Java 11
2. Spring 5.2.8.RELEASE
3. Spring Boot 2.3.2.RELEASE
4. Maven 3.5.2

2. HandlerFunction

Spring HandlerFunction is a functional interface with handle functional method. The role of HandlerFunction is performing HTTP requested task and returning response.
Find the source code for Spring HandlerFunction.
@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {
   Mono<T> handle(ServerRequest request);
} 
The handle functional method accepts ServerRequest and returns Mono<ServerResponse>.
Find the handler function that accepts ServerRequest and returns Mono<ServerResponse> as following.
@Component
public class BookHandler {
	@Autowired
	private BookService bookService;
	public Mono<ServerResponse> getBookById(ServerRequest request) {
		int id = Integer.parseInt(request.pathVariable("id"));
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
			.body(bookService.getBookById(id), Book.class);
	}	
} 
We can pass our handler function using lambda expression as well as method reference.
1. Using lambda expression.
serverRequest -> bookHandler.getBookById(serverRequest) 
2. Using method reference
bookHandler::getBookById 
The HandlerFunction can be equated with the body of a @RequestMapping method in the annotation-based programming model.

3. RouterFunction

a. RouterFunction functional interface :
Spring RouterFunction is a functional interface with route functional method. The role of RouterFunction is routing incoming requests to specified handler function.
Find the source code for Spring RouterFunction.
@FunctionalInterface
public interface RouterFunction<T extends ServerResponse> {
   Mono<HandlerFunction<T>> route(ServerRequest request);
   ------
} 
The route functional method accepts ServerRequest and returns Mono<HandlerFunction<T>>. The RouterFunction has some defaults methods that are and, andNest, andOther, andRoute, filter, accept. To instantiate RouterFunction, Spring provides RouterFunctions class.

b. RouterFunctions class :
Spring RouterFunctions class is the central entry point to functional web framework. The RouterFunctions class uses a builder-style API to build RouterFunction with its RouterFunctions.Builder inner class. The RouterFunctions.Builder provides methods such as GET, POST, PUT, DELETE, HEAD, PATCH etc. These methods returns RouterFunctions.Builder for further building the routes. They accept URL pattern, RequestPredicate and HandlerFunction as arguments whereas RequestPredicate is optional.
Find method declarations of GET, POST, PUT, DELETE method from RouterFunctions.Builder doc.
1. The GET method adds a route to the specified handlerFunction that handles all HTTP GET requests matching the specified URL pattern and predicate.
RouterFunctions.Builder	GET(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction) 
2. The POST method adds a route to the specified handlerFunction that handles all HTTP POST requests matching the specified URL pattern and predicate.
RouterFunctions.Builder	POST(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction) 
3. The PUT method adds a route to the specified handlerFunction that handles all HTTP PUT requests matching the specified URL pattern and predicate.
RouterFunctions.Builder	PUT(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction) 
4. The DELETE method adds a route to the specified handlerFunction that handles all HTTP DELETE requests matching the specified URL pattern and predicate.
RouterFunctions.Builder	DELETE(String pattern, RequestPredicate predicate, HandlerFunction<ServerResponse> handlerFunction) 


Find the sample code to build the router.
@Configuration
public class BookRouterConfig {
	@Bean
	public RouterFunction<ServerResponse> root(BookHandler bookHandler) {
		return RouterFunctions.route()
		  .GET("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), bookHandler::getBookById)
		  .build();
	}
} 
In the above code, handler function is being passed as method reference. We can also pass it as lambda expression as following.
@Bean
public RouterFunction<ServerResponse> root(BookHandler bookHandler) {
	return RouterFunctions.route()
          .GET("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), serverRequest -> bookHandler.getBookById(serverRequest))
	  .build();
} 

4. @EnableWebFlux

1. The @EnableWebFlux is added with @Configuration annotation. The @EnableWebFlux imports the Spring WebFlux configuration from WebFluxConfigurationSupport that enables use of annotated controllers and functional endpoints.
@Configuration
@EnableWebFlux
public class BookRouterConfig {
   ------
} 
2. We can customize the imported configurations of @EnableWebFlux by implementing WebFluxConfigurer interface and overriding its default methods such as configureHttpMessageCodecs(), configurePathMatching(), configureContentTypeResolver(), configureArgumentResolvers() etc.
@Configuration
@EnableWebFlux
public class MyConfiguration implements WebFluxConfigurer {
    @Override
    public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
      ------
    }
} 
3. There can be more than one classes annotated with @Configuration but only one class should be annotated with @EnableWebFlux.
4. In Spring Boot application, @EnableWebFlux is not required because Spring Boot applies WebFlux features by default.
5. In Spring Boot application, if we want to keep Spring Boot default WebFlux features and add some additional configurations, our @Configuration class should implement WebFluxConfigurer but without @EnableWebFlux.
6. In Spring Boot application, if we want complete control of Spring WebFlux, we can add @EnableWebFlux with @Configuration annotation.

5. Mono and Flux

The Mono and Flux are Reactor APIs from reactor.core.publisher package. The Mono completes successfully by emitting an element or with an error whereas the Flux completes successfully by emitting 0 to N element or with an error.
The Mono has methods as flatMap, flatMapMany, just, retry etc.
The Flux has methods as flatMap, flatMapIterable, fromArray, collectList etc.
As we know that our handler function accepts ServerRequest and returns Mono<ServerResponse>.
public Mono<ServerResponse> getBook(ServerRequest request) {
   ------
} 
Let us understand how to access request data from ServerRequest and prepare Mono<ServerResponse> to return by handler function.
1. Access request body as Mono.
Mono<Book> book = request.bodyToMono(Book.class); 
2. Access request body as Flux.
Flux<Book> books = request.bodyToFlux(Book.class); 
3. We can access request body using BodyExtractors, too.
Mono<Book> book = request.body(BodyExtractors.toMono(Book.class));
Flux<Book> books = request.body(BodyExtractors.toFlux(Books.class)); 
4. Access multipart data.
Mono<MultiValueMap<String, Part> map = request.multipartData();
Flux<Part> parts = request.body(BodyExtractors.toParts()); 
5. Prepare response.
Mono<ServerResponse> response = ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
			.body(BodyInserters.fromValue(book)); 

6. WebClient

Spring WebClient is a non-blocking reactive client API that performs HTTP requests. To send HTTP requests, the WebClient has methods such as get(), post(), put(), delete() etc. Find the sample code to send HTTP GET request to server over given URL.
WebClient client = WebClient.create("http://localhost:8080");
public void getBookByIdDemo() {
	  int id = 101;
	  client.get()
		.uri("/books/" + id)
		.exchange()
		.flatMap(res -> res.bodyToMono(Book.class))
		.subscribe(book -> System.out.println("GET: " + book),
			   err -> System.out.println(err.getMessage())); 
} 


7. Complete Example with Spring Boot

Here we will create a Spring Boot WebFlux application using functional programming model to serve HTTP GET, POST, PUT and DELETE requests. We will also create client using WebClient to send requests.
1. Server Code
pom.xml
<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.3.2.RELEASE</version>
	<relativePath/>
</parent>
<properties>
	<java.version>11</java.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-webflux</artifactId>
	</dependency>
</dependencies> 
BookRouterConfig.java
package com.concretepage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
@Configuration
@EnableWebFlux
@ComponentScan("com.concretepage")
public class BookRouterConfig {
	@Bean
	public RouterFunction<ServerResponse> root(BookHandler bookHandler) {
		return RouterFunctions.route()
		  .GET("/books", bookHandler::getAllBooks)
		  .GET("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), bookHandler::getBookById)
		  .POST("/add", RequestPredicates.contentType(MediaType.APPLICATION_JSON), bookHandler::addBook)
		  .PUT("/update", RequestPredicates.contentType(MediaType.APPLICATION_JSON), bookHandler::updateBook)
		  .DELETE("/books/{id}", RequestPredicates.accept(MediaType.TEXT_PLAIN), bookHandler::deleteBookById)
		  .build();
	}
} 
BookHandler.java
package com.concretepage;
import java.net.URI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
@Component
public class BookHandler {
	@Autowired
	private BookService bookService;
	
	public Mono<ServerResponse> getAllBooks(ServerRequest request) {
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
			.body(bookService.getAllBooks(), Book.class);
	}
	public Mono<ServerResponse> getBookById(ServerRequest request) {
		int id = Integer.parseInt(request.pathVariable("id"));
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
			.body(bookService.getBookById(id), Book.class);
	}	
	public Mono<ServerResponse> addBook(ServerRequest request) {
		return request.bodyToMono(Book.class)
				.flatMap(book -> bookService.addBook(book))
				.flatMap(newBook -> ServerResponse.created(URI.create("/books/" + newBook.getId()))
						.contentType(MediaType.APPLICATION_JSON)
						.build());
	}
	public Mono<ServerResponse> updateBook(ServerRequest request) {
		return request.bodyToMono(Book.class)
				.flatMap(book -> bookService.updateBook(book))
				.flatMap(modBook -> ServerResponse.ok()
						.contentType(MediaType.APPLICATION_JSON)
						.body(BodyInserters.fromValue(modBook)));
	}	
	public Mono<ServerResponse> deleteBookById(ServerRequest request) {
		return bookService.deleteBookById(Integer.parseInt(request.pathVariable("id")))
				.flatMap(val -> {
					if (val == true) {
					    return ServerResponse.noContent().build();
					}
					return ServerResponse.notFound().build();
				});
	}	
} 
BookService.java
package com.concretepage;
import java.util.stream.Stream;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class BookService {
	public Flux<Book> getAllBooks() {
		return Flux.fromStream(Stream.of(new Book(201, "Python"), new Book(202, "HTML")));
	}	
	public Mono<Book> getBookById(int id) {
		return Mono.just(new Book(id, "Java"));
	}	
	public Mono<Book> addBook(Book book) {
		return Mono.just(new Book(102, book.getName()));
	}
	public Mono<Book> updateBook(Book book) {
		return Mono.just(new Book(book.getId(), book.getName() +" - updated"));
	}	
	public Mono<Boolean> deleteBookById(int id) {
		return Mono.just(true);
	}	
} 
Book.java
package com.concretepage;
public class Book {
	private int id;
	private String name;
	public Book() {}
	public Book(String name) {
		this.name = name;
	}	
	public Book(int id, String name) {
		this.id = id;
		this.name = name;
	}
        //Sets and Gets        

	@Override
	public String toString() {
		return getId() + ", " + getName();
	}
} 

2. Client Code
BookWebClient.java
package com.concretepage;
import org.springframework.web.reactive.function.client.WebClient;
public class BookWebClient {
	private WebClient client = WebClient.create("http://localhost:8080");
	
	public void getAllBooksDemo() {
		  client.get()
			.uri("/books")
			.exchange()
			.flatMapMany(res -> res.bodyToFlux(Book.class))
			.collectList()
			.subscribe(books -> books.forEach(b -> System.out.println(b)));
	}	
	public void getBookByIdDemo() {
		  int id = 101;
		  client.get()
		        .uri("/books/" + id)
			.exchange()
			.flatMap(res -> res.bodyToMono(Book.class))
			.subscribe(book -> System.out.println("GET: " + book),
				   err -> System.out.println(err.getMessage())); 
	}	
	public void addBookDemo() {
		  client.post()
			.uri("/add")
			.bodyValue(new Book("Spring"))
			.exchange()
			.subscribe(res -> System.out.println("POST: "
			  + res.statusCode() + ", " + res.headers().asHttpHeaders().getLocation())); 
	}
	public void updateBookDemo() {
		  client.put()
			.uri("/update")
			.bodyValue(new Book(103, "Android"))
			.exchange()
		        .flatMap(res -> res.bodyToMono(Book.class))
			.subscribe(book -> System.out.println("PUT: " + book)); 
	}
	public void deleteBookByIdDemo() {
		  int id = 104;
		  client.delete()
			.uri("/books/" + id)
			.exchange()	
		        .subscribe(res -> System.out.println("DELETE: " + res.statusCode()));
	}	
} 

3. Run Application
Application.java
package com.concretepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
		BookWebClient bwc = new BookWebClient();
		bwc.getAllBooksDemo();
		bwc.getBookByIdDemo();
		bwc.addBookDemo();
		bwc.updateBookDemo();
		bwc.deleteBookByIdDemo();		
	}
} 
To run the application, download the code and go to root directory of the project using command prompt and run the command.
mvn spring-boot:run 
Find the print screen of the output.
Spring WebFlux Functional Endpoints

8. References

Web on Reactive Stack
Spring WebFlux Auto-configuration
Mono
Flux

9. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us