Friday, December 18, 2020

Spring WebFlux Example Using Annotation-Based Programming - Spring Web Reactive

The post Spring Web Reactive Framework - Spring WebFlux Tutorial gives an overview of Spring web reactive. Building on that knowledge in this post we’ll see a Spring web reactive example using Spring WebFlux annotation-based programming model where @Controller and @RestController components use annotations to express request mappings, request input, exception handling, and more. The application built here is a RESTful web service with Spring Webflux and also includes a WebClient consumer of that service. Application uses Spring Boot and runs on the default Netty server.


Maven dependency for Spring WebFlux

<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.netjs</groupId>
  <artifactId>reactive</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>reactive</name>
  <url>http://maven.apache.org</url>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.5.RELEASE</version>
    <relativePath/>
  </parent>
    
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <jdk.version>1.10</jdk.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

The dependency you need to add is spring-boot-starter-webflux which takes care of getting all the jar dependencies like reactor-core, reactive-streams, spring-webflux along with the Netty server. Spring Boot 2 uses Netty by default with WebFlux because Netty is more widely used in the async, non-blocking space and also provides both client and server that can share resources.

Spring reactive WebFlux example – annotation-based programming

For Spring WebFlux annotation-based programming model you need to provide the following components-

  1. Controller class- You can define controller beans using a standard Spring bean definition. The @Controller stereotype allows for auto-detection, aligned with Spring general support for detecting @Component classes in the classpath and auto-registering bean definitions for them.
  2. @RequestMapping- The @RequestMapping annotation is used to map requests to controllers methods. It has various attributes to match by URL, HTTP method, request parameters, headers, and media types.

    There are also HTTP method specific shortcut variants of @RequestMapping:

    • @GetMapping
    • @PostMapping
    • @PutMapping
    • @DeleteMapping
    • @PatchMapping

In the example here we also have a model bean User and a reactive repository that is exposed in the handler class for the data operations. To keep the application simple we’ll use a Map to store data rather than going to an actual reactive repository like reactive Mongo.

Spring Reactive WebFlux application - Model bean

The model bean used is User.java class.

public class User {
 private long id;
 private String firstName;
 private String lastName;
 private String email;
 
 public User() {
   
 }
 public User(long id, String firstName, String lastName, String email) {
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
  this.email = email;
 }
 public long getId() {
  return id;
 }
 public void setId(long id) {
  this.id = id;
 }
 public String getFirstName() {
  return firstName;
 }
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 public String getLastName() {
  return lastName;
 }
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
}

Spring Reactive WebFlux application – Repository

public interface UserRepository {
  Mono<User> getUserById(int id);
  
  Flux<User> getAllUsers();
  
  Mono<Void> saveUser(Mono<User> user);
}

As you can see there are three methods-

  1. getUserById fetches a single User by Id. This method returns a Mono.
  2. getAllUsers fetches all the Users. This method returns a Flux.
  3. saveUser method saves the passed User object. This method returns Mono<Void> which is an empty Mono that emits a completion signal when the user has been read from the request and stored.

UserRepositoryImpl.java

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.netjs.model.User;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Repository
public class UserRepositoryImpl implements UserRepository {
    
  Map<Integer, User> userMap = new ConcurrentHashMap<Integer, User>(); 
  public UserRepositoryImpl(){
    userMap.put(1, new User(1, "Robert", "Ludlum", "rl@rl.com"));
    userMap.put(2, new User(2, "John", "Grisham", "jg@jg.com"));
    userMap.put(3, new User(3, "James", "Patterson", "jp@jp.com"));
  }

  @Override
  public Mono<User> getUserById(int id) {
    return Mono.justOrEmpty(userMap.get(id));
  }

  @Override
  public Flux<User> getAllUsers() {
    // get as stream
    return Flux.fromStream(userMap.values().stream());
  }

  @Override
  public Mono<Void> saveUser(Mono<User> user) {    
    Mono<User> userMono = user.doOnNext(value->{
      userMap.put((userMap.keySet().size() +1), value);            
    } );
    return userMono.then();
  }    
}

Spring Reactive WebFlux application – Controller Class

Controller end points are grouped together in a UserController class. UserController class is annotated with @RestController annotation.

import org.netjs.model.User;
import org.netjs.repository.UserRepository;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class UserController {
  @Autowired
  private UserRepository userRepository;

  @GetMapping("/user")
  public Flux<User> listUser() {
    Flux<User> user = userRepository.getAllUsers();
    return user;
  }
    
  @GetMapping("/user/{id}")
  Mono<User> findById(@PathVariable int id) {
    return this.userRepository.getUserById(id);
  }

  @PostMapping("/user")
  Mono<Void> create(@RequestBody Mono<User> userStream) {
    return this.userRepository.saveUser(userStream).then();
  }
}
  • listUser controller end point returns all User objects found in the repository as Flux.
  • createUser is a handler function that stores a new User contained in the request body. UserRepository.saveUser(User) returns Mono<Void>.
  • getUser is a handler function that returns a single user, identified via the path variable id. That user is retrieved from the repository using that id.

Creating a WebClient

For reactive applications, Spring framework offers the WebClient class, which is non-blocking. We’ll use a WebClient implementation to consume our RESTful service.

import java.util.List;

import org.netjs.model.User;
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.Flux;
import reactor.core.publisher.Mono;

public class UserWebClient {
  private WebClient client = WebClient.create("http://localhost:8080");
  // For getting all users
  private Mono<ClientResponse> result = client.get()
        .uri("/user")
        .accept(MediaType.APPLICATION_JSON_UTF8)
        .exchange();
    
  /*    // Getting user by ID
  private Mono<User> singleUser = client.get()
          .uri("/user/{1}")
          .accept(MediaType.APPLICATION_JSON_UTF8)
          .exchange()
          .flatMap(res -> res.bodyToMono(User.class));*/
    

  public List<User> getResult() {
    Flux<User> userList = result.flatMapMany(res -> res.bodyToFlux(User.class));
    return userList.collectList().block();
  }
}

Making the Application Executable

We’ll configure the Spring WebFlux application as a standalone application using the @SpringBootApplication convenience annotation.

@SpringBootApplication
@ComponentScan(basePackages={"org.netjs.reactive", "org.netjs.repository"})
public class Application {

 public static void main(String[] args) {
  SpringApplication.run(Application.class, args);
  UserWebClient uwc = new UserWebClient();
  System.out.println(uwc.getResult());
 }
}

The main() method uses Spring Boot’s SpringApplication.run() method to launch an application.

Running the Spring WebFlux REST application

Now we have the WebClient to consume the Restful service and the Spring boot configuration to launch the application.

You can run the above class as stand alone Java application.

You can also run the application from the command line with Gradle or Maven. You can also build a single executable JAR file that contains all the necessary dependencies, classes, and resources, and run that.

With Maven, you can run the application using mvn spring-boot:run command from terminal. Or you can build the JAR file with mvn clean package. Then you can run the JAR file- java -jar target/reactive-0.0.1-SNAPSHOT.jar

When using mvn spring-boot:run command from your project directory to launch the application, on the successful start of the application you should see on the console that the netty server is started and listening on default port 8080.

2018-11-09 12:10:47.325  INFO 9492 --- [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/user],methods=[POST]}" onto reactor.core.publisher.Mono<java.lang.Void> org.netjs.reactive.UserController.create(reactor.core.publisher.Mono<org.netjs.model.User>)
2018-11-09 12:10:47.327  INFO 9492 --- [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/user],methods=[GET]}" onto public reactor.core.publisher.Flux<org.netjs.model.User> org.netjs.reactive.UserController.listUser()
2018-11-09 12:10:47.328  INFO 9492 --- [           main] s.w.r.r.m.a.RequestMappingHandlerMapping : Mapped "{[/user/{id}],methods=[GET]}" onto reactor.core.publisher.Mono<org.netjs.model.User> org.netjs.reactive.UserController.findById(int)

2018-11-09 12:10:47.429  INFO 9492 --- [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.reactive.resource.ResourceWebHandler]
2018-11-09 12:10:47.430  INFO 9492 --- [           main] o.s.w.r.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.reactive.resource.ResourceWebHandler]
2018-11-09 12:10:47.696  INFO 9492 --- [           main] o.s.w.r.r.m.a.ControllerMethodResolver   : Looking for @ControllerAdvice: org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext@72c8e7b: startup date [Fri Nov 09 12:10:45 IST 2018]; root of context hierarchy
2018-11-09 12:10:48.533  INFO 9492 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-11-09 12:10:48.945  INFO 9492 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext     : Started HttpServer on /0:0:0:0:0:0:0:0:8080

Since Spring boot uses Netty Server by default so application is deployed to the server automatically and you can access it by giving the following URL in browser to fetch all the Users -

http://localhost:8080/user
Spring WebFlux annotation based example

To get user by ID

http://localhost:8080/user/2
Spring reactive webflux annotation based example

To save a user

To save a user you can use the following curl command.

curl localhost:8080/user/create -H "Content-Type: application/json" -X POST -d "{\"id\":4, \"firstName\":\"Dean\",\"lastName\":\"Koontz\", \"email\":\"dk@dk.com\"}"  
Spring WebFlux annotation based example

That's all for this topic Spring WebFlux Example Using Annotation-Based Programming - Spring Web Reactive. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring Transaction Management Example - @Transactional Annotation and JDBC
  2. Spring MVC Configuring Multiple View Resolvers Example
  3. Spring JdbcTemplate Select Query Example
  4. Sending Email Using Spring Framework Example
  5. Spring Component Scan to Automatically Discover Beans

You may also like-

  1. Difference Between @Controller And @RestController Annotations in Spring
  2. Dependency Injection in Spring Framework
  3. Using depends-on Attribute in Spring
  4. How to Inject Prototype Scoped Bean in Singleton Bean
  5. EnumSet in Java With Examples
  6. Java ThreadLocal Class With Examples
  7. Difference Between equals() Method And equality Operator == in Java
  8. Java Program to Get All The Tables in a DB Schema

1 comment: