Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战

推荐 Spring Boot/Cloud 视频:

  • Java 微服务实践 - Spring Boot
  • Java 微服务实践 - Spring Cloud

示例源码

SpringBoot基于WebFlux注解的RestAPIs示例源码 githup

    Spring 5通过引入一个名为Spring WebFlux的全新反应框架,采用了反应式编程范式。

Spring WebFlux是一个自下而上的异步框架。它可以使用Servlet 3.1非阻塞IO API以及其他异步运行时环境(如netty或afow)在Servlet容器上运行。

它将与Spring MVC一起使用。是的,Spring MVC不会去任何地方。它是开发人员长期使用的流行Web框架。

但是您现在可以在新的反应框架和传统的Spring MVC之间进行选择。您可以根据使用情况选择使用其中任何一种。

WebFlux解析实战

小编学习的途径是先直接到官网看看,于是看到了最明显的区别就是下图
Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第1张图片
图先放这里,先不着急,先看下,有一个印象,后面会说到。

SpringBoot2.0新特性

  • 编程语言Java8+,和当前火爆的Kotlin
  • 底层框架Spring Framwork 5.0.x
  • 全新特性Web Flux(小编认为我们学习SpringBoot2.0就是学习这个)

我们分析下2.0的新特性为什么编程语言要从Java8开始呢?

  • 一个重要的原因就是使用Java8的Lambda表达式和Stream流处理
  • 包括Spring Framwork 5.0.x也是要用到Java8的新特性.
  • SpringBoot1.0是仅支持Servlet Containers->Servlet API属于传统方式
  • SpringBoot2.0在支持1.0的特性上,同时添加了一个新特性就是WebFlux,可以使用Netty及Servlet3.1作为容器,基于 Reactive Streams 流处理。

那么我们在分析Servlet3.0之前和3.0的区别?

  • 3.0之前Servlet 线程会一直阻塞,只有当业务处理完成并返回后时结束 Servlet线程。
  • 3.0规范其中一个新特性是异步处理支持,即是在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,在不生成响应的情况下返回至容器

举一个例子

  • 那么当有200个线程同时并发在处理,那么当来201个请求的时候,就已经处理不了,因为所有的线程都阻塞了。这是3.0之前的处理情况
  • 而3.0之后异步处理是怎样处理呢?学过Netty通信框架的同学会比较容易理解一点,Servlet3.0类似于Netty一样就一个boss线程池和work线程池,boss线程只负责接收请求,work线程只负责处理逻辑。那么servlet3.0规范中,这200个线程只负责接收请求,然后每个线程将收到的请求,转发到work线程去处理。因为这200个线程只负责接收请求,并不负责处理逻辑,故不会被阻塞,而影响通信,就算处理非常耗时,也只是对work线程形成阻塞,所以当再来请求,同样可以处理,其主要应用场景是针对业务处理较耗时的情况可以减少服务器资源的占用,并且提高并发处理速度。

Spring WebFlux Functional

Spring Framework 5.0支持完全异步和非阻塞的WebFlux,并且不需要Servlet API(与Spring MVC不同)。
Spring WebFlux支持2种不同的编程模型:

  • 基于注释的@Controller
  • 功能与Java 8 lambda风格

在本教程中,我们将介绍带有Functional的 WebFlux 。
对于从WebFlux开始,SpringBoot支持集合依赖:spring-boot-starter-webflux。

使用Spring WebFlux Functional,我们使用{ HandlerFunctions,RouterFunctions}来开发。
1. HandlerFunctions
HandlerFunctions将处理传入的HTTP请求ServerRequest,并返回一个Mono

@Component
public class CustomerHandler {
	
	...
	
    public Mono getAll(ServerRequest request) {
		...
		return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(customers, Customer.class);
    }
    
    ...
    
    public Mono putCustomer(ServerRequest request) {
		...
		return responseMono
                .flatMap(cust -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(fromObject(cust)));
    }
 
    ...
	
    public Mono deleteCustomer(ServerRequest request) {
		...
		return responseMono
                .flatMap(strMono -> ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(fromObject(strMono)));
    }
}

2. RouterFunction
RouterFunction处理所有传入的请求。需要一个ServerRequest,并返回一个。如果请求与特定路由匹配,则返回处理函数; 否则它返回一个空的Mono。Mono



@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);
    }
    
}

好了,当你已经读到这里,相信已经对SpringBoot1.0和SpringBoot2.0有一个比较清晰的认识了(当用过Netty通信框架类的童鞋一定是非常清晰的,如果还不清晰,就要补补课了),所以我们不得不说SpringBoot2.0的性能一定是比1.0有所提升的。不过各有所爱,企业具体技术选型还要看业务需求,不能盲目追求新技术,毕竟新技术还不太稳定,没有被大规模的实践。好了,理论的知识就先讲到这里,开始实战编码吧。

项目实战

在本教程中,我们创建一个SpringBoot项目,如下所示:
Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第2张图片

步骤:

  • 创建SpringBoot项目
  • 创建数据模型
  • 实现存储库
  • 实现Spring WebFlux API
  • 运行并检查结果

1.创建SpringBoot项目

使用SpringToolSuite,创建一个具有Reactive Web依赖关系的SpringBoot项目:
Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第3张图片

创建后检查pom.xml:




	
		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.创建数据模型

创建客户数据模型:

package com.javasampleapproach.webflux.model;
 
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 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.创建存储库

3.1定义接口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);
}

3.2创建CustomerRepository

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) {
		Mono customerMono =  monoCustomer.doOnNext(customer -> {
            // do post
            custStores.put(customer.getCustId(), customer);
            
            // log on console
            System.out.println("########### POST:" + customer);
        });
		
		return customerMono.then();
	}
	
	@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.just("Delete Succesfully!");
	}
}

4.实现Spring WebFlux API

4.1 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);
    }
    

4.2 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 ServerResponse.ok().build(customerRepository.saveCustomer(customer));
    }
    
    /**
     *	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"));
    	
    	// get customer from repository 
    	Mono responseMono = customerRepository.deleteCustomer(customerId);
    	
    	// build response
		return responseMono
                .flatMap(strMono -> ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(fromObject(strMono)));
    }
}

5.运行并检查结果

使用命令行构建和运行SpringBoot项目:{ mvn clean install,mvn spring-boot:run}。

  • 获取所有客户要求: http://localhost:8080/api/customer
    Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第4张图片

  • 提出GET客户要求: http://localhost:8080/api/customer/1
    Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第5张图片

  • 发出POST请求: http://localhost:8080/api/customer/post
    Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第6张图片

  • 提出PUT请求: http://localhost:8080/api/customer/put/3
    Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第7张图片

  • 发出删除请求: http://localhost:8080/api/customer/delete/1

Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第8张图片

  • 向所有客户提出要求: http://localhost:8080/api/customer

Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第9张图片

源代码

SpringBoot基于WebFlux注解的RestAPIs示例源码 githup

专家推荐

“随着微服务架构的发展,Spring Cloud 使用得越来越广泛。驰狼课堂 Spring Boot 快速入门,Spring Boot 与Spring Cloud 整合,docker+k8s,大型电商商城等多套免费实战教程可以帮您真正做到快速上手,将技术点切实运用到微服务项目中。” 
关注公众号,每天精彩内容,第一时间送达!
Spring Boot 基础系列教程 | 第三十九篇:SpringBoot基于WebFlux注解的RestAPIs项目实战_第10张图片

你可能感兴趣的:(Spring,Boot)