SpringWebflux详细讲解
1.背景
2.Spring5 框架新功能(Webflux)
2.1.SpringWebflux 介绍
(1)webFlux是 Spring5 添加的新模块,用于 web 的开发,功能和 SpringMVC 类似的,Webflux 使用 当前一种比较流程响应式编程出现的框架。
在spring5的jar包中和架构图中我们都可以看见
(2)使用传统 web 框架,比如 SpringMVC,这些基于 Servlet 容器,Webflux 是一种异步非阻塞的框架,
异步非阻塞的框架在 Servlet3.X以后才支持,核心是基于 Reactor 的API 实现的。
(3)异步与同步,阻塞与非阻塞的理解
这里做一个简单的通俗的解释:
1.异步和同步针对调用者,调用者发送请求,如果等着对方回应之后才去做其他事情就是同步,如果发送请求之后不等着对方回应就去做其他事情就是异步
2.阻塞和非阻塞针对被调用者,被调用者接受到请求之后,做完请求任务之后才给出反馈就是阻塞,接受到请求之后马上给出反馈然后再去做事情就是非阻塞
如果想深入理解并实践应用异步同步阻塞非阻塞等技术,可能会涉及到网络编程、socket、BIO、NIO、AIO、Netty等技术,大家可以学习之前讲的《网络编程系列课程》
(4)Webflux 特点:
特性一、 异步非阻塞
SpringMVC是同步阻塞的IO模型,资源浪费相对来说比较严重,当我们在处理一个比较耗时的任务时,
例如:上传一个比较大的文件,首先,服务器的线程一直在等待接收文件,在这期间它就像个傻子一样等在那儿(放学别走),什么都干不了,好不容易等到文件来了并且接收完毕,
我们又要将文件写入磁盘,在这写入的过程中,这根线程又要等到文件写完才能去干其它的事情。这一前一后的等待,浪费了大量的资源。
而Spring WebFlux就是来解决这个问题的,
Spring WebFlux可以做到异步非阻塞。还是上面那上传文件的例子,
Spring WebFlux是这样做的:线程发现文件还没准备好,就先去做其它事情,当文件准备好之后,通知这根线程来处理,
当接收完毕写入磁盘的时候(根据具体情况选择是否做异步非阻塞),写入完毕后通知这根线程再来处理(异步非阻塞情况下)。
这个设计相对于SpringMVC而言,可以大大节省系统资源。
特性二、 响应式(reactive)函数编程
Spring5 框架基于 java8,Webflux 使用 Java8 函数式编程方式实现路由请求,如果你觉得java8的lambda写起来很爽,那么,你会再次喜欢上Spring WebFlux,
因为它支持函数式编程,得益于对于reactive-stream的支持(通过reactor框架来实现的)。
特性三、 不再拘束于Servlet容器
以前,我们的应用都运行于Servlet容器之中,例如我们大家最为熟悉的Tomcat, Jetty...等等。
而现在Spring WebFlux不仅能运行于传统的Servlet容器中(前提是容器要支持Servlet3.1,因为非阻塞IO是使用了Servlet3.1的特性),还能运行在支持NIO的Netty和Undertow中。
(5)Webflux与SpringMVC的区别
区别一: 两个框架都可以使用注解方式,都可以运行在 Tomet 等容器中
区别二: SpringMVC 采用命令式编程,Webflux 采用异步响应式编程
具体的可以看后面的案例演示,可能会更好理解!
(6)总结(面试的时候很重要)
特点
1.webflux是一个异步非阻塞的Web框架,它能够充分利用多核CPU的硬件资源去处理大量的并发请求
2.内部使用的是响应式编程,以Reactor库为基础,基于异步和事件驱动,可以让我们在不扩充硬件资源的前提下,提升系统的吞吐量和伸缩性。
3.不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。
应用场景
1.特别适合在IO密集型的服务中,比如微服务网关。
2.IO 密集型包括:磁盘IO密集型, 网络IO密集型,
微服务网关就属于网络 IO 密集型,使用异步非阻塞式编程模型,能够显著地提升网关对下游服务转发的吞吐量。
选WebFlux还是Spring MVC
1.WebFlux不是 Spring MVC的替代方案!虽然 WebFlux 也可以被运行在 Servlet 容器上(需是 Servlet 3.1+ 以上的容器),
但是 WebFlux 主要还是应用在异步非阻塞编程模型,而 Spring MVC 是同步阻塞的,
如果你目前在 Spring MVC 框架中大量使用非同步方案,那么,WebFlux 才是你想要的,否则,使用 Spring MVC 才是你的首选。
2.在微服务架构中,Spring MVC 和 WebFlux 可以混合使用,比如已经提到的,对于那些 IO 密集型服务(如网关),我们就可以使用 WebFlux 来实现。
2.2.响应式编程(Java 实现)
package com.ldp.testWebflux; import java.util.Observable; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/15 7:14 * @description <p> * 什么是响应式编程 * 响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便 * 地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。 * 电子表格程序就是响应式编程的一个例子。单元格可以包含字面值或类似"=B1+C1"的公 * 式,而包含公式的单元格的值会依据其他单元格的值的变化而变化。 * </p> */ public class Test01Observer extends Observable { /** * @param args */ public static void main(String[] args) { Test01Observer observer = new Test01Observer(); // 添加观察者 observer.addObserver((o, arg) -> { System.out.println("通知数据变化---1"); }); observer.addObserver((o, arg) -> { System.out.println("通知数据变化---2"); }); observer.addObserver((o, arg) -> { System.out.println("通知数据变化---3"); }); // 数据变化 observer.setChanged(); // 通知 observer.notifyObservers(); System.out.println("代码执行完成.....4"); } }
2.3.响应式编程(Reactor 实现)
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
package com.ldp.reactor; import org.junit.jupiter.api.Test; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; import java.util.stream.Stream; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/15 7:40 * @description * <p> * 单词认识 * 1.mono * 英 [ˈmɒnəʊ] 美 [ˈmɑːnoʊ] * adj.单声道的 n.单声道录音(或放音)系统 * 2.flux * 英 [flʌks] 美 [flʌks] * n.不断的变动;不停的变化;通量;流动 v.熔化;熔解;流出 * </P> * Publisher 发送(生产者,可以类比为把数据放入redis) * subscribe 订阅(消费者,可以类比为从redis中读取数据) * <p> * 1.响应式编程操作中,Reactor 是满足 Reactive 规范框架 * 2.Reactor 有两个核心类,Mono 和 Flux,这两个类实现接口 Publisher,提供丰富操作API; * 2.1.Flux 对象实现发布者,返回 N 个元素; * 2.2.Mono 实现发布者,返回 0 或者 1 个元素 * <p> * 3.Flux 和 Mono 都是数据流的发布者,使用 Flux 和 Mono 都可以发出三种数据信号: * 元素值 * 错误信号 * 完成信号 * 错误信号和完成信号:都代表终止信号,终止信号用于告诉订阅者数据流结束了; * 错误信号终止数据流同时把错误信息传递给订阅者; */ public class Test01Reactor { /** * 简单使用 */ @Test public void test01() { // just方法直接声明 // 2.1.Flux 对象实现发布者,返回 N 个元素 (实际开发中我们可以放入产品列表数据) Flux.just("张三", "李四", "王五", "赵六").subscribe(System.out::println); System.out.println("===================================================="); // 2.2.Mono 实现发布者,返回 0 或者 1 个元素 (实际开发中我们可以放入根据id查询的单条数据) Mono.just("张无忌").subscribe(System.out::println); } /** * Flux 的formXXX声明 */ @Test public void test02() { System.out.println("数组....."); Integer[] array = {1, 2, 3, 4}; Flux.fromArray(array).subscribe(System.out::println); System.out.println("集合....."); List<Integer> list = Arrays.asList(array); Flux.fromIterable(list).subscribe(System.out::println); System.out.println("流....."); Stream<Integer> stream = list.stream(); Flux.fromStream(stream).subscribe(System.out::println); } }
2.4.SpringWebflux 执行流程和核心 API
SpringWebflux 执行过程和 SpringMVC 相似的
1.SpringWebflux 核心控制器 DispatchHandler,实现接口 WebHandler,WebHandler 有一个方法handle
SpringWebflux 里面 DispatcherHandler,负责请求的处理
1. HandlerMapping:请求查询到处理的方法
2. HandlerAdapter:真正负责请求处理
3. HandlerResultHandler:响应结果处理
源码解读
2.5.SpringWebflux(基于注解编程模型)
用法与springmvc几乎是一样的,只是service的实现类有点不一样,这里以Product模型案例讲解
备注
SpringMVC 方式实现,同步阻塞的方式,基于 SpringMVC+Servlet+Tomcat
SpringWebflux 方式实现,异步非阻塞 方式,基于 SpringWebflux+Reactor+Netty
步骤一:编写Product模型
package com.ldp.reactor.model; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 6:42 * @description */ public class Product { private Integer id; private String productNo; private String productName; public Product() { } public Product(Integer id, String productNo, String productName) { this.id = id; this.productNo = productNo; this.productName = productName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getProductNo() { return productNo; } public void setProductNo(String productNo) { this.productNo = productNo; } public String getProductName() { return productName; } public void setProductName(String productName) { this.productName = productName; } @Override public String toString() { return "Product{" + "id=" + id + ", productNo='" + productNo + '\'' + ", productName='" + productName + '\'' + '}'; } }
步骤二:编写IProductService接口
package com.ldp.reactor.service; import com.ldp.reactor.model.Product; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 6:58 * @description */ public interface IProductService { //根据 id 查询用户 Mono<Product> getProductById(int id); //查询所有用户 Flux<Product> getAllProduct(); //添加用户 Mono<Void> saveProductInfo(Mono<Product> user); }
步骤三:编写ProductServiceImpl实现
注意:与springmvc相比较就是这里不一样,因此在能熟练运用springmvc的情况下使用SpringWebflux,应该还是比较容易
package com.ldp.reactor.service.impl; import com.ldp.reactor.model.Product; import com.ldp.reactor.service.IProductService; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 7:01 * @description */ @Service public class ProductServiceImpl implements IProductService { //创建 map 集合存储数据 public Map<Integer, Product> products = new HashMap<>(); public ProductServiceImpl() { for (int i = 1; i <= 10; i++) { this.products.put(i, new Product(i, "P1001" + i, "苹果-" + i)); } } @Override public Mono<Product> getProductById(int id) { return Mono.justOrEmpty(this.products.get(id)); } @Override public Flux<Product> getAllProduct() { return Flux.fromIterable(this.products.values()); } @Override public Mono<Void> saveProductInfo(Mono<Product> ProductMono) { return ProductMono.doOnNext(product -> { //向 map 集合里面放值 int id = products.size() + 1; products.put(id, product); }).thenEmpty(Mono.empty()); } }
步骤四:ProductController的编写
package com.ldp.reactor.controller; import com.ldp.reactor.model.Product; import com.ldp.reactor.service.IProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 7:11 * @description */ @RestController public class ProductController { @Autowired private IProductService productService; /** * id 查询 * * @param id * @return */ @GetMapping("/product/{id}") public Mono<Product> getProductId(@PathVariable int id) { return productService.getProductById(id); } /** * @return */ @GetMapping("/product") public Flux<Product> getProducts() { return productService.getAllProduct(); } /** * @param Product * @return */ @PostMapping("/saveProduct") public Mono<Void> saveProduct(@RequestBody Product Product) { Mono<Product> ProductMono = Mono.just(Product); return productService.saveProductInfo(ProductMono); } }
步骤五:编写接口测试
package com.ldp.reactor.controller; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import com.alibaba.fastjson.JSON; import com.ldp.reactor.Base; import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.Map; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 7:15 * @description */ public class ProductControllerTest extends Base { @Test public void getProductId() { String url = urlLocal + "/product/11"; System.out.println("请求地址:" + url); HttpRequest request = HttpUtil.createRequest(Method.GET, url); request.setConnectionTimeout(60 * 1000); String response = request.execute().body(); System.out.println("响应结果:" + response); } @Test public void getProducts() { String url = urlLocal + "/product"; System.out.println("请求地址:" + url); HttpRequest request = HttpUtil.createRequest(Method.GET, url); request.setConnectionTimeout(60 * 1000); String response = request.execute().body(); System.out.println("响应结果:" + response); } @Test public void saveProduct() { String url = urlLocal + "/saveProduct"; System.out.println("请求地址:" + url); HttpRequest request = HttpUtil.createRequest(Method.POST, url); Map<String, Object> map = new HashMap<>(); map.put("productNo", "P203"); map.put("productName", "习惯03"); String jsonStr = JSON.toJSONString(map); request.body(jsonStr); System.out.println("请求参数:" + jsonStr); request.setConnectionTimeout(60 * 1000); String response = request.execute().body(); System.out.println("响应结果:" + response); } }
2.6.SpringWebflux(基于函数式编程模型)
1.在使用函数式编程模型操作时候,需要自己初始化服务器
2.基于函数式编程模型时候,有两个核心接口:
RouterFunction(实现路由功能,请求转发给对应的 handler)
HandlerFunction(处理请求生成响应的函数)。
3.核心任务定义两个函数式接口的实现并且启动需要的服务器。
4.SpringWebflux 请求和响应不再是ServletRequest和ServletResponse ,而是
ServerRequest 和 ServerResponse
在基于注解实现的情况下具体实现步骤如下
步骤一:编写Handle处理器
package com.ldp.reactor.handler; import com.ldp.reactor.model.Product; import com.ldp.reactor.service.IProductService; import com.ldp.reactor.service.impl.ProductServiceImpl; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import static org.springframework.web.reactive.function.BodyInserters.fromObject; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 7:51 * @description */ public class ProductHandler { private IProductService productService; public ProductHandler() { this.productService = new ProductServiceImpl(); } //根据 id 查询 public Mono<ServerResponse> getProductById(ServerRequest request) { //获取 id 值 int ProductId = Integer.valueOf(request.pathVariable("id")); //空值处理 Mono<ServerResponse> notFound = ServerResponse.notFound().build(); //调用 service 方法得到数据 Mono<Product> productMono = this.productService.getProductById(ProductId); //把 ProductMono 进行转换返回 //使用 Reactor 操作符 flatMap return productMono .flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(fromObject(person))) .switchIfEmpty(notFound); } //查询所有 public Mono<ServerResponse> getAllProducts(ServerRequest serverRequest) { //调用 service 得到结果 Flux<Product> products = this.productService.getAllProduct(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(products, Product.class); } //添加 public Mono<ServerResponse> saveProduct(ServerRequest request) { //得到 Product 对象 Mono<Product> ProductMono = request.bodyToMono(Product.class); return ServerResponse.ok().build(this.productService.saveProductInfo(ProductMono)); } }
步骤二:编写启动服务端
package com.ldp.reactor; import com.ldp.reactor.handler.ProductHandler; import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.netty.http.server.HttpServer; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.web.reactive.function.server.RequestPredicates.*; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 7:58 * @description */ public class FluxServer { //1 创建 Router 路由 public RouterFunction<ServerResponse> routingFunction() { //创建 hanler 对象 ProductHandler handler = new ProductHandler(); //设置路由 return RouterFunctions.route( GET("/product/{id}").and(accept(APPLICATION_JSON)), handler::getProductById) .andRoute(GET("/product").and(accept(APPLICATION_JSON)), handler::getAllProducts) .andRoute(POST("/saveProduct").and(accept(APPLICATION_JSON)), handler::saveProduct); } //2 创建服务器完成适配 public void createReactorServer() { //路由和 handler 适配 RouterFunction<ServerResponse> route = routingFunction(); HttpHandler httpHandler = RouterFunctions.toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); //创建服务器 HttpServer httpServer = HttpServer.create(); httpServer.handle(adapter).bindNow(); } //最终调用 public static void main(String[] args) throws Exception { FluxServer server = new FluxServer(); server.createReactorServer(); System.out.println("enter to exit"); System.in.read(); } }
步骤三:测试
测试方式一:使用http模拟请求的方式,与springmvc的测试方式一样(略)
测试方式二:使用flux客户端的方式
package com.ldp.reactor; import com.ldp.reactor.model.Product; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; /** * @author 姿势帝-博客园 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 01/16 11:03 * @description */ public class FluxClient { public static void main(String[] args) { //调用服务器地址 WebClient webClient = WebClient.create("http://localhost:13220"); //根据 id 查询 String id = "1"; Product productResult = webClient.get().uri("/product/{id}", id) .accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(Product .class) .block(); System.out.println("查询结果:" + productResult); //查询所有 Flux<Product> results = webClient.get().uri("/product") .accept(MediaType.APPLICATION_JSON).retrieve().bodyToFlux(Product.class); results.map(product -> product) .buffer().doOnNext(System.out::println).blockFirst(); } }