Spring 5 : Functional Web Framework | Code Factory
5 min readApr 15, 2020
Reference Link : Link
Donate : Link
Spring WebFlux framework introduces a new functional web framework built using reactive principles.
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.example</groupId>
<artifactId>spring5-functional-web-crud-api</artifactId>
<version>1.0-SNAPSHOT</version><build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories><properties>
<spring.version>5.0.0.RELEASE</spring.version>
</properties><dependencyManagement>
<dependencies>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>Bismuth-RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor.ipc</groupId>
<artifactId>reactor-netty</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
</dependencies></project>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Logger name="org.springframework" level="info" />
<Logger name="org.springframework.web" level="debug" />
<Root level="error">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
Client.java
package com.example;import java.net.URI;
import java.util.List;import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;import com.example.model.Employee;/**
* @author code.factory
*/
public class Client {private ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());public static void main(String[] args) throws Exception {
Client client = new Client();
client.createPerson();
client.printAllPeople();
}public void printAllPeople() {
URI uri = URI.create(String.format("http://%s:%d/person", Server.HOST, Server.PORT));
ClientRequest request = ClientRequest.method(HttpMethod.GET, uri).build();Flux<Employee> people = exchange.exchange(request)
.flatMapMany(response -> response.bodyToFlux(Employee.class));Mono<List<Employee>> peopleList = people.collectList();
System.out.println(peopleList.block());
}public void createPerson() {
URI uri = URI.create(String.format("http://%s:%d/person", Server.HOST, Server.PORT));
Employee jack = new Employee("Emp3", 23);ClientRequest request = ClientRequest.method(HttpMethod.POST, uri)
.body(BodyInserters.fromObject(jack)).build();Mono<ClientResponse> response = exchange.exchange(request);System.out.println(response.block().statusCode());
}}
Server.java
package com.example;import static org.springframework.web.reactive.function.server.RequestPredicates.DELETE;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.POST;
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.RouterFunctions.toHttpHandler;import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
import org.springframework.http.server.reactive.ServletHttpHandlerAdapter;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;import reactor.ipc.netty.http.server.HttpServer;import com.example.controller.EmployeeHandler;
import com.example.repository.EmployeeRepository;
import com.example.repository.IEmployeeRepository;/**
* @author code.factory
*/
public class Server {public static final String HOST = "localhost";public static final int PORT = 8080;public static void main(String[] args) throws Exception {
Server server = new Server();
server.startReactorServer();
// server.startTomcatServer();System.out.println("Press ENTER to exit.");
System.in.read();
}public RouterFunction<ServerResponse> routingFunction() {
IEmployeeRepository repository = new EmployeeRepository();
EmployeeHandler handler = new EmployeeHandler(repository);return route(GET("/person/{id}"), handler::getPerson)
.and(route(GET("/person"), handler::listPeople))
.and(route(POST("/person"), handler::createPerson))
.and(route(PUT("/person/{id}"), handler::updatePerson))
.and(route(DELETE("/person/{id}"), handler::deletePerson));}public void startReactorServer() throws InterruptedException {
RouterFunction<ServerResponse> route = routingFunction();
HttpHandler httpHandler = toHttpHandler(route);ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
HttpServer server = HttpServer.create(HOST, PORT);
server.newHandler(adapter).block();
}public void startTomcatServer() throws LifecycleException {
RouterFunction<?> route = routingFunction();
HttpHandler httpHandler = toHttpHandler(route);Tomcat tomcatServer = new Tomcat();
tomcatServer.setHostname(HOST);
tomcatServer.setPort(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();
}}
EmployeeHandler.java
package com.example.controller;import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;import com.example.model.Employee;
import com.example.repository.IEmployeeRepository;import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;/**
* @author code.factory
*
*/
public class EmployeeHandler {private final IEmployeeRepository repository;public EmployeeHandler(IEmployeeRepository repository) {
this.repository = repository;
}public Mono<ServerResponse> getPerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<ServerResponse> notFound = ServerResponse.notFound().build();
Mono<Employee> personMono = this.repository.getPerson(personId);
return personMono
.flatMap(person -> ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person)))
.switchIfEmpty(notFound);
}public Mono<ServerResponse> listPeople(ServerRequest request) {
Flux<Employee> people = this.repository.allPeople();
return ServerResponse.ok().contentType(APPLICATION_JSON).body(people, Employee.class);
}public Mono<ServerResponse> createPerson(ServerRequest request) {
Mono<Employee> person = request.bodyToMono(Employee.class);
return ServerResponse.ok().build(this.repository.savePerson(person));
}public Mono<ServerResponse> updatePerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<Employee> person = request.bodyToMono(Employee.class);
Mono<Employee> personMono = this.repository.getPerson(personId);
if(personMono.equals(Mono.empty())) {
return ServerResponse.notFound().build();
}
return ServerResponse.ok()
.contentType(APPLICATION_JSON)
.body(this.repository.updatePerson(personId, person), Employee.class);
}public Mono<ServerResponse> deletePerson(ServerRequest request) {
int personId = Integer.valueOf(request.pathVariable("id"));
Mono<Employee> personMono = this.repository.getPerson(personId);
if(personMono.equals(Mono.empty())) {
return ServerResponse.notFound().build();
}
return ServerResponse.ok().build(this.repository.deletePerson(personId));
}
}
Employee.java
package com.example.model;import com.fasterxml.jackson.annotation.JsonProperty;/**
* @author
*
*/
public class Employee {private final String name;private final int age;public Employee(@JsonProperty("name") String name, @JsonProperty("age") int age) {
this.name = name;
this.age = age;
}public String getName() {
return this.name;
}public int getAge() {
return this.age;
}@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
EmployeeRepository.java
package com.example.repository;import java.util.HashMap;
import java.util.Map;import com.example.model.Employee;import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;/**
* @author code.factory
*/
public class EmployeeRepository implements IEmployeeRepository {private final Map<Integer, Employee> people = new HashMap<>();public EmployeeRepository() {
this.people.put(1, new Employee("Emp1", 21));
this.people.put(2, new Employee("Emp2", 22));
}@Override
public Mono<Employee> getPerson(int id) {
return Mono.justOrEmpty(this.people.get(id));
}@Override
public Flux<Employee> allPeople() {
System.out.println(this.people.toString());
return Flux.fromIterable(this.people.values());
}@Override
public Mono<Void> savePerson(Mono<Employee> personMono) {
return personMono.doOnNext(person -> {
int id = people.size() + 1;
people.put(id, person);
System.out.println("Saved " + person);
}).thenEmpty(Mono.empty());
}@Override
public Mono<Employee> updatePerson(int personId, Mono<Employee> personMono) {
return personMono.doOnNext(person -> {
people.put(personId, person);
System.out.println("Updated " + person);
});
}@Override
public Mono<Void> deletePerson(int id) {
this.people.remove(id);
return Mono.empty();
}
}
IEmployeeRepository.java
package com.example.repository;import com.example.model.Employee;import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;/**
* @author code.factory
*/
public interface IEmployeeRepository {Mono<Employee> getPerson(int id);Flux<Employee> allPeople();Mono<Void> savePerson(Mono<Employee> person);
Mono<Employee> updatePerson(int id, Mono<Employee> person);
Mono<Void> deletePerson(int id);}
First run Server.java
after that you can check service using 2 ways.
1. Using postman
or any other API testing software
2. Run Client.java
Here i test this service using postman
Note : use Content-Type
= application/json
HTTP GET http://localhost:8080/person
HTTP GET http://localhost:8080/person/1
HTTP POST http://localhost:8080/person
HTTP PUT http://localhost:8080/person/
HTTP DELETE http://localhost:8080/person/3