Spring 5 : Functional Web Framework | Code Factory

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

--

--