Spring Reactive REST API

By Arvind Rai, February 02, 2019
This page will walk through Spring Reactive REST API example. Spring WebFlux is Spring reactive-stack web framework introduced in Spring 5.0. Spring WebFlux is fully non-blocking, supports Reactive Streams back pressure, and runs on the servers such as Netty, Undertow, and Servlet 3.1+ containers. Spring WebFlux is introduced for non-blocking web stack to handle concurrency with a small number of threads and scale with fewer hardware resources.
Reactor is the reactive library for Spring WebFlux whose operators are aligned with ReactiveX. Reactor provides following Publishers.
Mono: A Reactive Steams Publisher that emits 0 to 1 element or an error.
Flux: A Reactive Steams Publisher that emits 0 to N elements or an error.

Mono and Flux uses operators that support non-blocking back pressure. Reactor focuses on server-side Java and has been developed in collaboration with Spring.
Here on this page we will create Spring Reactive RESTful web services. We will create functional endpoints as well as annotated controllers for our REST application.

Technologies Used

Find the technologies being used in our example.
1. Java 11
2. Spring 5.1.3.RELEASE
3. Spring Boot 2.1.1.RELEASE
4. JUnit 5
5. Maven 3.5.2
6. Eclipse 2018-09

Create Functional Endpoints

Spring functional programming is the alternative of annotation-based programming. Spring WebFlux includes WebFlux.fn, a lightweight functional programming. It uses functions to route and handle requests and contracts are designed for immutability. To handle the requests we create handler functions and to route the requests to handler functions, we create router.
Here we will create a Spring Boot Reactive REST API example using functional programming. Find the project structure.
Spring Reactive REST API
To work with Spring WebFlux, we need to resolve spring-boot-starter-webflux dependency.
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-webflux</artifactId>
</dependency> 
In WebFlux.fn, we create handler function to handle HTTP requests. Handler function takes ServerResponse and returns delayed ServerResponse as Mono<ServerResponse>. Find our handler function.
BookHandler.java
package com.concretepage;
import java.util.function.Predicate;
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 {
	public Mono<ServerResponse> getBookById(ServerRequest request) {
		int id = Integer.parseInt(request.pathVariable("id"));
		Predicate<Book> predicate = b -> b.getId() == id;
		Book book = Book.getAllBooks().stream().filter(predicate).findFirst().get();
		
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
			.body(BodyInserters.fromObject(book));
	}
	public Mono<ServerResponse> getAllBooks(ServerRequest request) {
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
			.body(BodyInserters.fromObject(Book.getAllBooks()));
	}	
} 
Handler function is equivalent to the body of a @RequestMapping method in the annotation-based programming model.
To route the requests to handler function, we need to create router function using RouterFunction.
BookRouter.java
package com.concretepage;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
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
public class BookRouter {
	@Bean
	public RouterFunction<ServerResponse> root(BookHandler bookHandler) {
		return RouterFunctions.route()
		  .GET("/book/{id}", accept(MediaType.TEXT_PLAIN), bookHandler::getBookById)
		  .GET("/allbooks", accept(MediaType.TEXT_PLAIN), bookHandler::getAllBooks)
		  .build();
	}
} 
Router function is equivalent to @RequestMapping annotation. RouterFunctions.route() returns a builder to create router.
return RouterFunctions.route()
  .GET("/book/{id}", accept(MediaType.TEXT_PLAIN), bookHandler::getBookById)
  .build(); 
We can also write above code as following.
return RouterFunctions.route(
	RequestPredicates.GET("/book/{id}").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
	bookHandler::getBookById
   ); 
Find other files used in our demo application.
Book.java
package com.concretepage;
import java.util.Arrays;
import java.util.List;
public class Book {
	private int id;
	private String name;
	public Book(int id, String name) {
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public String getName() {
		return name;
	}
	public static List<Book> getAllBooks() {
		return Arrays.asList(
			new Book(100, "Java Tutorials"),
			new Book(200, "Spring Tutorials"),
			new Book(300, "Angular Tutorials")
		);
	}
}
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);
	}
} 
Find the URLs to access web services of our demo application.
http://localhost:8080/book/200
http://localhost:8080/allbooks 
Find the print screen of the output.
Spring Reactive REST API
Find the Maven file used in demo application.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.springframework</groupId>
    <artifactId>spring-reactive</artifactId>
    <version>0.1.0</version>
    <parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.1.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>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-api</artifactId>
			<version>5.3.2</version>
			<scope>test</scope>
		</dependency>	
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-engine</artifactId>
			<version>5.3.2</version>
			<scope>test</scope>
		</dependency>	
		<dependency>
			<groupId>org.junit.jupiter</groupId>
			<artifactId>junit-jupiter-params</artifactId>
			<version>5.3.2</version>
			<scope>test</scope>
		</dependency>		    
		<dependency>
			<groupId>org.junit.platform</groupId>
			<artifactId>junit-platform-launcher</artifactId>
			<version>1.3.2</version>
			<scope>test</scope>
		</dependency>		
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project> 

Create Annotated Controllers

Spring WebFlux uses same annotation-based programming model as in Spring MVC using @Controller or @RestController annotations. Spring provides @EnableWebFlux to be annotated at JavaConfig class level with @Configuration annotation. @EnableWebFlux annotation imports Spring WebFlux configuration from WebFluxConfigurationSupport class in our configuration class.

Here we will create a Spring Boot Reactive REST API example using annotated controller. Find the JavaConfig used in our demo application.
AppConfig.java
package com.concretepage;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;

@EnableWebFlux
@Configuration
@ComponentScan("com.concretepage")
public class AppConfig {

} 
Find the controller class.
BookController.java
package com.concretepage;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
public class BookController {
	@Autowired
	private BookService service; 
	
	@GetMapping("/book/{id}")
	public Mono<Book> getBookById(@PathVariable("id") Integer id) {
        return service.getBookById(id);
	}
	@GetMapping("/allbooks")
	public Mono<List<Book>> getAllBooks() {
        return service.getAllBooks(); 
	}	
} 
Find the service class.
BookService.java
package com.concretepage;
import java.util.List;
import java.util.function.Predicate;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Service
public class BookService {
   public Mono<Book> getBookById(int id) {
	Predicate<Book> predicate = b -> b.getId() == id;
	Book book = Book.getAllBooks().stream().filter(predicate).findFirst().get();
        return Mono.just(book);
   }
   public Mono<List<Book>> getAllBooks() {
        return Mono.just(Book.getAllBooks());
   }
} 

AbstractReactiveWebInitializer

If we are not using Spring Boot, we need AbstractReactiveWebInitializer to deploy WAR to any Servlet 3.1 container. AbstractReactiveWebInitializer class wraps an HttpHandler with ServletHttpHandlerAdapter and registers that as a Servlet. We create the app initializer class extending AbstractReactiveWebInitializer as following.
WebAppInitializer.java
package com.concretepage;
import org.springframework.web.server.adapter.AbstractReactiveWebInitializer;

public class WebAppInitializer extends AbstractReactiveWebInitializer {
	@Override
	protected Class<?>[] getConfigClasses() {
		return new Class[] { AppConfig.class };
	}
} 

Create Client with WebClient

Spring WebFlux provides a reactive, non-blocking WebClient for HTTP requests. WebClient has functional, fluent reactive API to create client. Find the client code to consume RESTful web service in our demo application.
BookWebClient.java
package com.concretepage;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

public class BookWebClient {
	private WebClient client = WebClient.create("http://localhost:8080");

	public String fetchBookById() {
		Mono<ClientResponse> result = client.get()
			.uri("/book/200")
			.accept(MediaType.APPLICATION_JSON)
			.exchange();	
		
		return result.flatMap(res -> res.bodyToMono(String.class)).block();
	}
	public String fetchAllBooks() {
		Mono<ClientResponse> result = client.get()
			.uri("/allbooks")
			.accept(MediaType.APPLICATION_JSON)
			.exchange();	
		
		return result.flatMap(res -> res.bodyToMono(String.class)).block();
	}	
} 
To test the client application we can call it in our Application class as following.
Application.java
@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);

		BookWebClient bwc = new BookWebClient();
		System.out.println(bwc.fetchBookById());
		System.out.println("---------------------");
		System.out.println(bwc.fetchAllBooks());
	}
} 
Find the output in console.
{"id":200,"name":"Spring Tutorials"}
---------------------
[{"id":100,"name":"Java Tutorials"},{"id":200,"name":"Spring Tutorials"},{"id":300,"name":"Angular Tutorials"}] 

Test with WebTestClient

Spring Test provides non-blocking, reactive WebTestClient to connect to a server over HTTP. WebTestClient uses reactive WebClient internally to provide fluent API to verify responses. Find the test class used in demo application.
BookAppTest.java
package com.concretepage;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BookAppTest {
	@Autowired
	private WebTestClient webTestClient;

	@Test
	public void getBookByIdTest() {
	  webTestClient
		.get().uri("/book/200")
		.accept(MediaType.APPLICATION_JSON)
		.exchange()
		.expectStatus().isOk()
		.expectBody(String.class);
	}
	@Test
	public void getAllBooksTest() {
	  webTestClient
		.get().uri("/allbooks")
		.accept(MediaType.APPLICATION_JSON)
		.exchange()
		.expectStatus().isOk()
		.expectBody(List.class);
	}	
} 

References

Spring WebFlux
Building a Reactive RESTful Web Service

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us