In this post we’ll see a Spring web reactive example using Spring WebFlux functional programming model. 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 run on the default Netty server.
- Refer Spring Web Reactive Framework - Spring WebFlux Tutorial to know more about the Spring reactive web framework.
- Maven dependency for Spring WebFlux
- Spring WebFlux example – functional endpoint
- Reactive WebFlux application - Model bean
- Spring Reactive WebFlux application – Repository
- Spring Reactive WebFlux application – Handler functions
- Spring Reactive WebFlux application – Router functions
- Creating a WebClient
- Running Spring WebFlux application as a standalone application
- 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 WebFlux example – functional endpoint
For Spring WebFlux functional programming model you need to provide the following components-
- Handler function- Incoming HTTP requests are handled by a HandlerFunction, which is essentially a function that takes a ServerRequest and returns a Mono<ServerResponse>. It is recommended to group related handler functions into a handler or controller class.
- RouterFunction- For mapping incoming requests to appropriate handler functions RouterFunction is used. RouterFunction takes a ServerRequest, and returns a Mono<HandlerFunction>. If a request matches a particular route, a handler function is returned, or otherwise an empty Mono is returned. RouterFunction has a similar purpose as the @RequestMapping annotation in the annotation-based programming model.
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.
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; public class UserRepositoryImpl implements UserRepository { Map<Integer, User> userMap = new ConcurrentHashMap<Integer, User>(); public UserRepositoryImpl(){ // adding entries to Map 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 – Handler functions
Handler functions are grouped together in a UserHandler class.
import org.netjs.model.User; import org.netjs.repository.UserRepository; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import static org.springframework.http.MediaType.APPLICATION_JSON; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class UserHandler { private final UserRepository userRepository; public UserHandler(UserRepository userRepository) { this.userRepository = userRepository; } public Mono<ServerResponse> listUser(ServerRequest request) { Flux<User> user = userRepository.getAllUsers(); return ServerResponse.ok().contentType(APPLICATION_JSON).body(user, User.class); } public Mono<ServerResponse> getUser(ServerRequest request) { int userId = Integer.valueOf(request.pathVariable("id")); Mono<ServerResponse> notFound = ServerResponse.notFound().build(); Mono<User> userMono = userRepository.getUserById(userId); return userMono.flatMap(user -> ServerResponse.ok() .contentType(APPLICATION_JSON) .body(BodyInserters.fromObject(user))) .switchIfEmpty(notFound); } public Mono<ServerResponse> createUser(ServerRequest request) { System.out.println("in create user"); Mono<User> user = request.bodyToMono(User.class); return ServerResponse.ok().build(userRepository.saveUser(user)); } }
listUser is a handler function that returns all User objects found in the repository as JSON.
createUser is a handler function that stores a new User contained in the request body. Since UserRepository.saveUser(User) returns Mono<Void> so build(Publisher<Void>) method is used to send a response when that completion signal is received, i.e. when the User has been saved.
getUser is a handler function that returns a single user, identified via the path variable id. That user is retrieved from the repository, and a JSON response is created if it is found. If it is not found, we use switchIfEmpty(Mono<T>) to return a 404 Not Found response.
Spring Reactive WebFlux application – Router functions
We also need to define router function that routes to the respective handler functions. We use method-references to refer to the handler functions.
@Configuration public class UserRouter { @Bean public RouterFunction<ServerResponse> route() { UserRepository repository = new UserRepositoryImpl(); UserHandler userHandler = new UserHandler(repository); return RouterFunctions .route(GET("/user/{id}").and(accept(APPLICATION_JSON)), userHandler::getUser) .andRoute(GET("/user").and(accept(APPLICATION_JSON)), userHandler::listUser) .andRoute(POST("/user/create").and(contentType(APPLICATION_JSON)), userHandler::createUser); } }
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(); } }
Running Spring WebFlux application as a standalone application
We’ll configure the Spring WebFlux application as a standalone application using the @SpringBootApplication convenience annotation.
@SpringBootApplication 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.
You can run the above class as stand alone Java application and it will display the result it gets through WebClient.
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 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-02 10:37:54.151 INFO 8664 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080 2018-11-02 10:37:54.151 INFO 8664 --- [ main] org.netjs.reactive.Application : Started Application in 4.033 seconds (JVM running for 15.002)
From the browser you can access the following URL 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\"}"Reference: https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-fn
That's all for this topic Spring WebFlux Example Using Functional 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-
No comments:
Post a Comment