Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Spring 5 Reactive Web

DZone's Guide to

Spring 5 Reactive Web

Interested in learning how to use Spring 5 to do reaction web development? Let this article serve as your one-stop shop on the topic.

· Web Dev Zone
Free Resource

Learn how to build modern digital experience apps with Crafter CMS. Download this eBook now. Brought to you in partnership with Crafter Software

Spring 5 - Spring webflux has a new functional reactive web framework which is nonblocking. We can build asynchronous, nonblocking, event-driven services, that can scale very well.

Migrating from blocking (imperative) style of coding to functional nonblocking reactive style of coding helps to define the business logic as asynchronous function calls. This can be done using Java8 method references or lambda expressions. Since the threads are nonblocked, processing power is used to the maximum.

Spring 5 is still in milestone release (5.0.0 M5) as of writing this.

The code for this is available on Github.

Create a Spring Boot Project

The easy way to create the Spring Boot project is through Spring initializer. Add these starters to your maven pom.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <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>
</dependencies>

Spring-boot-starter-webflux brings the spring-webflux, netty, and other required dependencies to the class path.

Create a simple UserRepository and User DTO classes that get users' data from a list. This is just a dummy repository bean, but in real time this could be from other data sources like RDBMS, MongoDB, or RestClient. JDBC drivers available today are not reactive by nature. Hence any call to a database would still be blocking the thread. MongoDB has a reactive client driver. Rest calls could be made nonblocking as illustrated further in the test reactive web services section.

public class User {
public User(){}

public User(Long id, String user) {
this.id = id;
this.user = user;
}

private Long id;
private String user;

public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
}

@Repository
public class UserRepository {
private final List<User> users = Arrays.asList(new User(1L, "User1"), new User(2L, "User2"));

public Mono<User> getUserById(String id) {
return Mono.justOrEmpty(users.stream().filter(user -> {
return user.getId().equals(Long.valueOf(id));
}).findFirst().orElse(null));
}

public Flux<User> getUsers() {
return Flux.fromIterable(users);
}
}

Mono and Flux are the reactive types provided by Project Reactor. Springs also supports other reactive stream implementations like RXJava. Mono and Flux are implementations of Publisher from Reactive streams. Mono is a publisher that emits zero or single value, Flux is a publisher that emits zero to n values. They are similar to Flowable and Observable in RXJava. They indicate streams that emit values on subscribing to these publishers.

GetUserById() returns a Mono<User> which means it emits a zero or single user whenever available, GetUsers() returns a Flux of users which means it emits a zero to n users whenever available.

Compared to the imperative style of programming, we are not actually returning the User/List<User> which will block the executing thread until User/List<User> is actually available to return from the method. We are just returning a reference to the Stream that may return User/List<Users> in the future.

Create a Handler Class With Functions That Handle the HTTP Request

@Service
public class UserHandler {
@Autowired
private UserRepository userRepository;

public Mono<ServerResponse> handleGetUsers(ServerRequest request) {
return ServerResponse.ok().body(userRepository.getUsers(), User.class);
}

public Mono<ServerResponse> handleGetUserById(ServerRequest request) {
return userRepository.getUserById(request.pathVariable("id"))
.flatMap(user -> ServerResponse.ok().body(Mono.just(user), User.class))
.switchIfEmpty(ServerResponse.notFound().build());
}
}

A handler class is like Service beans in Spring Web, where we write most of the business functionalities of the service. ServerResponse is like the ResponseEntity class in Spring Web - we can wrap the Response data, status code, headers, etc. in the ServerResponse object. ServerResponse has a lot of useful default methods in it like notFound()ok()accepted() created(), etc., which can be used to create different types of responses.

UserHandler has different methods that again return Mono<ServerResponse>; UserRepository.getUsers() returns a Flux<User>; and ServerResponse.ok().body(UserRepository.getUsers(), User.class) transforms this Flux<User> into Mono<ServerResponse>, which indicates a stream that emits ServerResponse whenever available. UserRepository.getUserById()  returns a Mono<User> and ServerResponse.ok().body(Mono.just(user), User.class) transforms this Mono<User> into Mono<ServerResponse>, which indicates a stream that emits ServerResponse whenever available.

ServerResponse.notFound().build()  returns a Mono<ServerResponse> which indicate a stream that emits 404 ServerResponse when no user is found with the given pathVariable.

In the imperative style, we block the thread until data is received, which makes the thread do nothing until the actual data arrives. In reactive programming, we define the stream that emits data and define the operations we perform on them when the data arrives. This way thread is not blocked. The available thread is picked for executing whenever the data is returned.

Create a Routes Class That Define the Routes of the Application

import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
@Configuration
public class Routes {
private UserHandler userHandler;

    public Routes(UserHandler userHandler) {
this.userHandler = userHandler;
}

@Bean
public RouterFunction<?> routerFunction() {
return route(GET("/api/user").and(accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUsers)
.and(route(GET("/api/user/{id}").and(accept(MediaType.APPLICATION_JSON)), userHandler::handleGetUserById));
}
}

RouterFunction is like classes with @RequestMapping in Spring Web. RouterFunction is used to define routes for the Spring5 application. RouterFunctions helper class has a useful method, like route, which can be used to define the route and build the RouterFunction object. RequestPredicates has a lot of useful methods like GET, POST, path, queryParam ,accept, headers, contentType, etc. to define the routes and build the RouterFunction. Each Route is mapped to a handler method that has to be called when the appropriate HttpRequest is received.
Spring5 also supports the @RequestMapping type of controllers that define the handler mappings for the applications. We can write a controller method as shown below to create a similar API in the @RequestMapping style.

@GetMapping("/user") public Mono<ServerResponse> handleGetUsers() {}

Mono<ServerResponse> has to be returned from the controller method.

RouterFunction provides the DSL kind of routing capabilities for the application. At this point in time, mixing both types is not supported by Springs.


Create a HttpServerConfig Class That Creates a HttpServer Class

import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import reactor.ipc.netty.http.server.HttpServer;
@Configuration
public class HttpServerConfig {
@Autowired
private Environment environment;

@Bean
public HttpServer httpServer(RouterFunction<?> routerFunction) {
HttpHandler httpHandler = RouterFunctions.toHttpHandler(routerFunction);
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create("localhost", Integer.valueOf(environment.getProperty("server.port")));
server.newHandler(adapter);
return server;
}
}

This creates a netty HttpServer on a defined port in the application properties. Spring supports other servers also like Tomcat and undertow. Since netty is asynchronous and event-driven by nature, it is better for reactive applications. Tomcat uses Java NIO to implement servlet specs. Netty is an implementation of NIO which is optimized for asynchronous, event-driven nonblocking IO applications.

The Tomcat server can also be used as shown in below code:

    Tomcat tomcatServer = new Tomcat();
    tomcatServer.setHostname("localhost");
    tomcatServer.setPort(Integer.valueOf(environment.getProperty("server.port")));
    Context rootContext = tomcatServer.addContext("", System.getProperty("java.io.tmpdir"));
    ServletHttpHandlerAdapter servlet = new ServletHttpHandlerAdapter(httpHandler);
    Tomcat.addServlet(rootContext, "httpHandlerServlet", servlet);
    rootContext.addServletMapping("/", "httpHandlerServlet");
    tomcatServer.start();


Create a Spring Boot Main Class That Boots the Application

@SpringBootApplication
public class Spring5ReactiveApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(Spring5ReactiveApplication.class, args);
}
}


Testing the Application

You can test the application with any HTTP testing tools, like Postman or CURL.
Spring test also has the feature to write an integration test for reactive services.

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public class UserTest {
@Autowired
private WebTestClient webTestClient;

@Test
public void test() throws IOException {
FluxExchangeResult<User> result = webTestClient.get().uri("/api/user").accept(MediaType.APPLICATION_JSON)
.exchange().returnResult(User.class);
assert result.getStatus().value() == 200;
List<User> users = result.getResponseBody().collectList().block();
assert users.size() == 2;
assert users.iterator().next().getUser().equals("User1");
}

@Test
public void test1() throws IOException {
User user = webTestClient.get().uri("/api/user/1")
.accept(MediaType.APPLICATION_JSON).exchange().returnResult(User.class).getResponseBody().blockFirst();
assert user.getId() == 1;
assert user.getUser().equals("User1");
}

@Test
public void test2() throws IOException {
webTestClient.get().uri("/api/user/10").accept(MediaType.APPLICATION_JSON).exchange().expectStatus()
.isNotFound();
}
}

WebTestClient is like the TestRestTemplate class which has methods to make rest calls to the Spring boot application and assert the responses. In the test profile, a Spring test creates a bean for TestRestTemplate. There is also a WebClient, just like in the RestTemplate in Spring Web. This can be used to make rest Calls reactive and nonblocking.

    WebClient.create("http://localhost:9000").get().uri("/api/user/1")
        .accept(MediaType.APPLICATION_JSON).exchange().flatMap(resp -> resp.bodyToMono(User.class)).block();

exchange()returns a Mono<ClientResponse> that indicates a stream that Emits clientResponse when available.

block() blocks the thread until the Mono returns User/List<User>, since this is the test case where we need the data to assert the response.

Spring Web is imperative which is easy to develop/debug. The decision to use Spring5 reactive or Spring Web imperative services has to be done wisely according to use case. In many cases, just imperative may work well, but in cases where high scalability is key, reactive nonblocking would be a better fit.

Crafter is a modern CMS platform for building modern websites and content-rich digital experiences. Download this eBook now. Brought to you in partnership with Crafter Software.

Topics:
spring 5 ,reactive ,web dev

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}