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.
- Refer Spring Web Reactive - Spring WebFlux Example Using Functional Programming for Spring reactive WebFlux functional programming example.
- Maven dependency for Spring WebFlux
- Spring reactive WebFlux example – annotation-based programming
- Spring Reactive WebFlux application - Model bean
- Spring Reactive WebFlux application – Repository
- Spring Reactive WebFlux application – Controller Class
- Creating a WebClient
- Making the Application Executable
- Running the Spring WebFlux REST application
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-
- 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.
- @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-
- getUserById fetches a single User by Id. This method returns a Mono.
- getAllUsers fetches all the Users. This method returns a Flux.
- 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/userTo get user by ID
http://localhost:8080/user/2To 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\"}"
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
You may also like-
any samples on how to unit test these?
ReplyDelete