Spring WebClient with Spring Webflux RestAPIs | SpringBoot 2

Spring-WebClient-with-Spring-Webflux-RestAPIs-SpringBoot-2

In the tutorial, JavaSampleApproach will introduce new client side Spring WebClient that offers a fully non-blocking and reactive alternative to the RestTemplate.

Related posts:
SpringBoot WebFlux Functional RestAPIs
SpringBoot WebFlux Annotation-based RestAPIs
How to use Spring RestTemplate client for consuming Restful WebService
SpringBoot WebFlux Test

I. Technologies

– Java: 1.8
– Maven: 3.3.9
– Spring Tool Suite: Version 3.9.0.RELEASE
– Spring Boot: 2.0.0.M4
– Spring Boot Starter Webflux

II. Spring WebClient

As mentioned above, reactive WebClient is an alternative solution to the RestTemplate for fully non-blocking and reactive.
If we have Spring WebFlux on classpath, we can use WebClient to work with remote RestAPIs.


import org.springframework.web.reactive.function.client.WebClient;
...

@Service
public class WebClientTransportImpl{
	
	private final WebClient webClient;

    public WebClientTransportImpl(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
        									.baseUrl("http://localhost:8080/api/customer").build();
    }

	public Mono getById(Long id) {
		
		return this.webClient.get().uri("/{id}", id)
                					.retrieve().bodyToMono(Customer.class);
	}
	
	...

III. Practice

In the tutorial, we create 2 SpringBoot projects for working {SpringWebClient, SpringWebFluxFunction-Server}:

spring webclient flux server- projects structure

Step do to:
– Create SpringBoot projects
– Create data model
– Implement Spring WebFlux server
– Implement Spring WebClient
– Run and check results

1. Create SpringBoot projects

Using SpringToolSuite, create 2 SpringBoot projects {SpringWebClient, SpringWebFluxFunction-Server} with spring-boot-starter-webflux dependency:


	
		org.springframework.boot
		spring-boot-starter-webflux
	

	
		org.springframework.boot
		spring-boot-starter-test
		test
	
	
		io.projectreactor
		reactor-test
		test
	



	
		
			org.springframework.boot
			spring-boot-maven-plugin
		
	



	
		spring-snapshots
		Spring Snapshots
		https://repo.spring.io/snapshot
		
			true
		
	
	
		spring-milestones
		Spring Milestones
		https://repo.spring.io/milestone
		
			false
		
	



	
		spring-snapshots
		Spring Snapshots
		https://repo.spring.io/snapshot
		
			true
		
	
	
		spring-milestones
		Spring Milestones
		https://repo.spring.io/milestone
		
			false
		
	

2. Create data model

Create data model Customer for 2 projects:

public class Customer {
	private long custId;
	private String firstname;
	private String lastname;
	private int age;
	
	public Customer(){}
	
	public Customer(long custId, String firstname, String lastname, int age){
		this.custId = custId;
		this.firstname = firstname;
		this.lastname = lastname;
		this.age = age;
	}
	
	public Customer(String firstname, String lastname, int age){
		this.firstname = firstname;
		this.lastname = lastname;
		this.age = age;
	}
 
	public long getCustId() {
		return custId;
	}
 
	public void setCustId(Long custId) {
		this.custId = custId;
	}
 
	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 int getAge() {
		return age;
	}
 
	public void setAge(int age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		String info = String.format("custId = %d, firstname = %s, lastname = %s, age = %d", custId, firstname, lastname, age);
		return info;
	}
}
3. Implement Spring WebFlux server

Refer at: SpringBoot WebFlux Functional RestAPIs

3.1 Implement Customer repository

– Create a interface CustomerRepository:

package com.javasampleapproach.webflux.repo;

import com.javasampleapproach.webflux.model.Customer;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface CustomerRepository {
	public Mono getCustomerById(Long id);
	public Flux getAllCustomers();
	public Mono saveCustomer(Mono customer);
	public Mono putCustomer(Long id, Mono customer);
	public Mono deleteCustomer(Long id);
}

– Implement CustomerRepositoryImpl:

package com.javasampleapproach.webflux.repo.impl;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.springframework.stereotype.Repository;

import com.javasampleapproach.webflux.model.Customer;
import com.javasampleapproach.webflux.repo.CustomerRepository;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Repository
public class CustomerRepositoryImpl implements CustomerRepository{
	private Map custStores = new HashMap();
	
	@PostConstruct
    public void initIt() throws Exception {
        custStores.put(Long.valueOf(1), new Customer(1, "Jack", "Smith", 20));
        custStores.put(Long.valueOf(2), new Customer(2, "Peter", "Johnson", 25));
    }

	@Override
	public Mono getCustomerById(Long id) {
		return Mono.just(custStores.get(id));
	}

	@Override
	public Flux getAllCustomers() {
		return Flux.fromIterable(this.custStores.values());
	}

	@Override
	public Mono saveCustomer(Mono monoCustomer) {
		return  monoCustomer.doOnNext(customer -> {
            // do post
            custStores.put(customer.getCustId(), customer);
            
            // log on console
            System.out.println("########### POST:" + customer);
        });
	}
	
	@Override
	public Mono putCustomer(Long id, Mono monoCustomer) {
		Mono customerMono =  monoCustomer.doOnNext(customer -> {
			// reset customer.Id
			customer.setCustId(id);
			
			// do put
			custStores.put(id, customer);
			
			// log on console
			System.out.println("########### PUT:" + customer);
        });
		
		return customerMono;
	}
	
	@Override
	public Mono deleteCustomer(Long id) {
		// delete processing
    	custStores.remove(id);
    	return Mono.empty();
	}
}

3.2 Implement WebFlux functional

– define RouterFunction:

package com.javasampleapproach.webflux.functional.router;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import com.javasampleapproach.webflux.functional.handler.CustomerHandler;

import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

import org.springframework.http.MediaType;

@Configuration
public class RoutingConfiguration {
	
    @Bean
    public RouterFunction monoRouterFunction(CustomerHandler customerHandler) {
        return route(GET("/api/customer").and(accept(MediaType.APPLICATION_JSON)), customerHandler::getAll)
        		.andRoute(GET("/api/customer/{id}").and(accept(MediaType.APPLICATION_JSON)), customerHandler::getCustomer)
        		.andRoute(POST("/api/customer/post").and(accept(MediaType.APPLICATION_JSON)), customerHandler::postCustomer)
                .andRoute(PUT("/api/customer/put/{id}").and(accept(MediaType.APPLICATION_JSON)), customerHandler::putCustomer)
                .andRoute(DELETE("/api/customer/delete/{id}").and(accept(MediaType.APPLICATION_JSON)), customerHandler::deleteCustomer);
    }
    
}

– Implement CustomerHandler:

package com.javasampleapproach.webflux.functional.handler;

import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;

import org.springframework.http.MediaType;

import com.javasampleapproach.webflux.model.Customer;
import com.javasampleapproach.webflux.repo.CustomerRepository;

import static org.springframework.web.reactive.function.BodyInserters.fromObject;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
public class CustomerHandler {
	
	private final CustomerRepository customerRepository;

	public CustomerHandler(CustomerRepository repository) {
		this.customerRepository = repository;
	}
	
	/**
	 * GET ALL Customers
	 */
    public Mono getAll(ServerRequest request) {
    	// fetch all customers from repository
    	Flux customers = customerRepository.getAllCustomers();
    	
    	// build response
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(customers, Customer.class);
    }
    
    /**
     * GET a Customer by ID 
     */
    public Mono getCustomer(ServerRequest request) {
    	// parse path-variable
    	long customerId = Long.valueOf(request.pathVariable("id"));
    	
    	// build notFound response 
		Mono notFound = ServerResponse.notFound().build();
		
		// get customer from repository 
		Mono customerMono = customerRepository.getCustomerById(customerId);
		
		// build response
		return customerMono
                .flatMap(customer -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(customer)))
                .switchIfEmpty(notFound);
    }
    
    /**
     * POST a Customer
     */
    public Mono postCustomer(ServerRequest request) {
    	Mono customer = request.bodyToMono(Customer.class);
        
        return customerRepository.saveCustomer(customer)
                .flatMap(cust -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(cust)));
    }
    
    /**
     *	PUT a Customer
     */
    public Mono putCustomer(ServerRequest request) {
    	// parse id from path-variable
    	long customerId = Long.valueOf(request.pathVariable("id"));
    	
    	// get customer data from request object
    	Mono customer = request.bodyToMono(Customer.class);
    	
		// get customer from repository 
		Mono responseMono = customerRepository.putCustomer(customerId, customer);
		
		// build response
		return responseMono
                .flatMap(cust -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(cust)));
    }

    /**
     *	DELETE a Customer
     */
    public Mono deleteCustomer(ServerRequest request) {
    	// parse id from path-variable
    	long customerId = Long.valueOf(request.pathVariable("id"));
    	
    	// build response
    	return ServerResponse.ok().build(customerRepository.deleteCustomer(customerId));
    }
}
4. Implement Spring WebClient
4.1 Implement WebClient transporter

– Define interface WebClientTransport:

package com.javasampleapproach.webclient;

import com.javasampleapproach.model.Customer;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface WebClientTransport {
	public Mono getById(Long id);
	public Flux getAll();
	public Mono save(Customer customer);
	public Mono update(Long id, Customer customer);
	public Mono delete(Long id);
}

– Implement WebClientTransportImpl:

package com.javasampleapproach.webclient.impl;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

import com.javasampleapproach.model.Customer;
import com.javasampleapproach.webclient.WebClientTransport;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class WebClientTransportImpl implements WebClientTransport{
	
	private final WebClient webClient;

    public WebClientTransportImpl(WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
        									.baseUrl("http://localhost:8080/api/customer").build();
    }

	@Override
	public Mono getById(Long id) {
		
		return this.webClient.get().uri("/{id}", id)
                					.retrieve().bodyToMono(Customer.class);
	}

	@Override
	public Flux getAll() {
		
		return webClient.get()
							.retrieve().bodyToFlux(Customer.class);
	}

	@Override
	public Mono save(Customer customer) {
		
		return webClient.post().uri("/post")
										.body(BodyInserters.fromObject(customer))
										.exchange().flatMap( clientResponse -> clientResponse.bodyToMono(Customer.class) );
	}

	@Override
	public Mono update(Long id, Customer customer) {
		
		return webClient.put().uri("/put/{id}", id)
										.body(BodyInserters.fromObject(customer))
										.exchange().flatMap(clientResponse -> clientResponse.bodyToMono(Customer.class));
	}

	@Override
	public Mono delete(Long id) {
		
		return webClient.delete().uri("/delete/{id}", id)
										.retrieve().bodyToMono(Void.class);
	}

}

4.2 Implement WebClient Application
package com.javasampleapproach;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.javasampleapproach.model.Customer;
import com.javasampleapproach.webclient.WebClientTransport;

@SpringBootApplication
public class SpringWebClientApplication implements CommandLineRunner{

	@Autowired
	WebClientTransport webClient;
	
	public static void main(String[] args) {
		SpringApplication.run(SpringWebClientApplication.class, args);
	}

	@Override
	public void run(String... args) throws Exception {
		
		// get all customers
		System.out.println("################### Get All Customers");
		webClient.getAll().subscribe(System.out::println);
		
		Thread.sleep(2000);
		
		// get a customer
		System.out.println("################### Get a Customer with id = 1");
		webClient.getById(new Long(1)).subscribe(System.out::println);
		
		Thread.sleep(2000);
		
		// post a customer
		System.out.println("################### Save a Customer");
		webClient.save(new Customer(3, "Mary", "Taylor", 27)).subscribe(System.out::println);
		
		Thread.sleep(2000);
		
		// update a customer
		System.out.println("################### Update a Customer");
		webClient.update(new Long(3), new Customer(3, "Amy", "Taylor", 24)).subscribe(System.out::println);
		
		Thread.sleep(2000);
		
		// delete a customer
		System.out.println("################### Delete a Customer with id = 1");
		webClient.delete(new Long(1)).subscribe(response -> System.out.println("Delete Successfuly!"));
		
		Thread.sleep(2000);
		
		// get all customers
		System.out.println("################### Get All Customers for final checking");
		webClient.getAll().subscribe(System.out::println);
		
		Thread.sleep(2000);
	}
}

– Open application.properties, configure: server.port=8081

5. Run and check results

Build and Run the SpringWebFluxFunction-Server project then SpringWebClient project by commandlines {mvn clean install, mvn spring-boot:run}.

-> Console logs from SpringWebClient:

INFO 1628 --- [ctor-http-nio-1] r.ipc.netty.tcp.BlockingNettyContext     : Started HttpServer on /0:0:0:0:0:0:0:0:8081
INFO 1628 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port(s): 8081
################### Get All Customers
custId = 1, firstname = Jack, lastname = Smith, age = 20
custId = 2, firstname = Peter, lastname = Johnson, age = 25
################### Get a Customer with id = 1
custId = 1, firstname = Jack, lastname = Smith, age = 20
################### Save a Customer
custId = 3, firstname = Mary, lastname = Taylor, age = 27
################### Update a Customer
custId = 3, firstname = Amy, lastname = Taylor, age = 24
################### Delete a Customer with id = 1
################### Get All Customers for final checking
custId = 2, firstname = Peter, lastname = Johnson, age = 25
custId = 3, firstname = Amy, lastname = Taylor, age = 24

IV. Sourcecode

SpringWebClient
SpringWebFluxFunctional



By grokonez | September 30, 2017.

Last updated on April 8, 2019.



Related Posts


1 thought on “Spring WebClient with Spring Webflux RestAPIs | SpringBoot 2”

  1. Many thanks for this post on Spring webflux. Best one on the net. Cleared up some things for me. Only example that shows post, put, and get.

    Thanks.

Got Something To Say:

Your email address will not be published. Required fields are marked *

*