Spring WebFlux 教程:如何构建反应式 Web 应用程序

反应式系统提供了我们在高数据流世界中所需的无与伦比的响应能力和可扩展性。然而,反应式系统需要经过专门培训的工具和开发人员来实现这些独特的程序架构。Spring WebFlux with Project Reactor 是一个专门为满足现代公司的响应式需求而构建的框架。

今天,我们将通过解释 WebFlux 如何与其他反应式堆栈工具配合、有何不同以及如何制作您的第一个应用程序来帮助您开始使用 WebFlux。

 

什么是反应式系统?

反应式系统是采用反应式架构模式设计的系统,该模式优先使用松耦合、灵活和可扩展的组件。它们的设计还考虑了故障解决方案,以确保即使出现故障,大部分系统仍能运行。

反应式系统专注于:

  • 反应性:最重要的是,反应性系统应该对任何用户输入做出快速响应。反应式系统倡导者认为,反应式有助于优化系统的所有其他部分,从数据收集到用户体验。

  • 弹性:反应式系统的设计应该能够预测系统故障。反应式系统期望组件最终会失效,并设计松散耦合的系统,即使几个单独的部件停止工作也能保持活动状态。

  • 弹性:反应式系统应该通过扩大或缩小以满足需求来适应工作负载的大小。许多反应式系统还将使用预测性扩展来预测和准备突然变化。实现弹性的关键是消除任何瓶颈并构建可以根据需要分片或复制组件的系统。

  • 消息驱动的通信:反应式系统的所有组件都是松散耦合的,每个组件之间都有硬边界。您的系统应该通过显式消息传递跨越这些边界进行通信。这些消息让不同的组件了解故障,并帮助他们将工作流委派给可以处理它的组件。

反应式和其他 Web 模式之间最显着的区别是反应式系统可以一次执行多个未阻塞的调用,而不是让一些调用等待其他调用。因此,响应式系统可以提高性能和响应速度,因为 Web 应用程序的每个部分都可以比必须等待另一部分更快地完成自己的部分。

 

什么是反应堆项目?

Project Reactor 是一个由 Pivotal 构建并由 Spring 提供支持的框架。它实现了反应式 API 模式,最著名的是反应式流规范。

如果您熟悉Java 8 Streams,您会很快发现 Stream 和 Flux(或其单元素版本 Mono)之间的许多相似之处。它们之间的主要区别在于 Fluxes 和 Monos 遵循一种publisher-subscriber模式并实现背压,而 Stream API 则没有。

背压是数据端点向数据生产者发出信号,表明它接收了太多数据的一种方式。这允许更好的流量管理和分配,因为它可以防止单个组件过度工作。

使用 Reactor 的主要优点是您可以完全控制数据流。您可以依靠订阅者在准备好处理信息时询问更多信息的能力,或者在发布者端缓冲一些结果,甚至使用没有背压的全推送方法。

在我们的反应式堆栈中,它位于 Spring Boot 2.0 和 WebFlux 之上:

示例反应式堆栈

堆栈:技术堆栈是用于创建 Web 或移动应用程序的软件产品和编程语言的组合。反应式堆栈是相同的,但用于创建反应式应用程序。

 

什么是 Spring WebFlux?

Spring WebFlux 是一个完全非阻塞、基于注解的 Web 框架,它构建在 Project Reactor 之上,它使得在 HTTP 层上构建响应式应用程序成为可能。WebFlux 使用新的路由器功能特性将函数式编程应用于 Web 层并绕过声明性控制器和请求映射。WebFlux 要求您将 Reactor 作为核心依赖项导入。

WebFlux 作为Spring MVC的响应式替代品在 Spring 5 中添加,并增加了对以下内容的支持:

  • 非阻塞线程:无需等待先前任务完成即可完成指定任务的并发线程。

  • Reactive Stream API:一种标准化工具,包括用于非阻塞背压的异步流处理选项。

  • 异步数据处理:当数据在后台处理并且用户可以不间断地继续使用正常的应用程序功能时。

最终WebFlux摒弃了SpringMVC的多请求线程模型,而是使用多EventLoop非阻塞模型来启用反应式、可扩展的应用程序。由于支持Netty、Undertow 和Servlet 3.1+ 容器等流行服务器,WebFlux 已成为反应式堆栈的关键部分。

Router功能

RouterFunction是标准springmvc中使用的@RequestMapping和@Controller注释样式的一种功能替代。

我们可以使用它将请求路由到处理程序函数:

  • 传统的路由定义

@RestController
public class ProductController {@RequestMapping("/product")public List<Product> productListing() {return ps.findAll();}
}
  • 函数式定义

@Bean
public RouterFunction<ServerResponse> productListing(ProductService ps) {return route().GET("/product", req -> ok().body(ps.findAll())).build();
}

你可以使用RouterFunctions.route()来创建路由,而不是编写完整的路由器函数。路由注册为spring的bean,因此可以在任何配置类中创建。路由器功能避免了由请求映射的多步骤过程引起的潜在副作用,而是将其简化为直接的路由器/处理程序链。这允许函数式编程实现反应式编程。

RequestMapping和Controller注释样式在WebFlux中仍然有效如果您对旧样式更熟悉,RouterFunctions只是解决方案的一个新选项。

WebClient 详解

项目中经常用到发送Http请求的客户端,如果你使用webflux那非常简单去创建一个Http请求。WebClient是WebFlux的反应式web客户端,它是从著名的rest模板构建的。它是一个接口,表示web请求的主要入口点,并支持同步和异步操作。WebClient主要用于反应式后端到后端通信。

您可以通过使用Maven导入标准WebFlux依赖项来构建和创建WebClient实例:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

创建实例

WebClient webClient = WebClient.create();
// 如果是调用特定服务的API,可以在初始化webclient 时使用,baseUrl
WebClient webClient = WebClient.create("https://github.com/1ssqq1lxr");

或者构造器方式初始化

WebClient webClient1 = WebClient.builder().baseUrl("https://github.com/1ssqq1lxr").defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json").defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient").build();
  • Get请求

Mono<String> resp = WebClient.create().method(HttpMethod.GET).uri("https://github.com/1ssqq1lxr").cookie("token","xxxx").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).retrieve().bodyToMono(String.class);
  • Post请求(表单)

 MultiValueMap<String, String> formData = new LinkedMultiValueMap();formData.add("name1","value1");formData.add("name2","value2");Mono<String> resp = WebClient.create().post().uri("http://www.w3school.com.cn/test/demo_form.asp").contentType(MediaType.APPLICATION_FORM_URLENCODED).body(BodyInserters.fromFormData(formData)).retrieve().bodyToMono(String.class);
  • Post请求(Body)

Book book = new Book();
book.setName("name");
book.setTitle("this is title");
Mono<String> resp = WebClient.create().post().uri("https://github.com/1ssqq1lxr").contentType(MediaType.APPLICATION_JSON_UTF8).body(Mono.just(book),Book.class).retrieve().bodyToMono(String.class);
  • 文件上传

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
HttpEntity<ClassPathResource> entity = new HttpEntity<>(new ClassPathResource("parallel.png"), headers);
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
arts.add("file", entity);
Mono<String> resp = WebClient.create().post().uri("http://localhost:8080/upload").contentType(MediaType.MULTIPART_FORM_DATA).body(BodyInserters.fromMultipartData(parts)).retrieve().bodyToMono(String.class);

Reactive Steam API

下篇文章给大家详细讲下Reactor3的API

Reactive Stream API是一个的函数集合,允许更智能的流数据流。它内置了对背压和异步处理的支持,确保应用程序最有效地利用计算机和组件资源。

反应流API有四个主要接口:

  • Publisher:根据链接订阅者的需求向他们发布事件。充当订户可以监视事件的中心链接点。

  • Subscriber:接收和处理发布服务器发出的事件。多个订阅服务器可以链接到单个发布服务器,并对同一事件做出不同的响应。订户可以设置为反应:

    • onNext,当它接收到下一个事件时。

    • onSubscribe,添加新订阅时

    • onError,当另一个订阅服务器发生错误时

    • onComplete,当一个订阅完成时

Server容器

WebFlux在Tomcat、Jetty、servlet3.1+容器以及Netty和Undertow等非Servlet运行时上都受支持。Netty最常用于异步和非阻塞设计,因此WebFlux将默认使用它。只需对Maven或Gradle构建软件进行简单的更改,就可以轻松地在这些服务器选项之间切换。

这使得WebFlux在它可以使用的技术方面具有高度的通用性,并允许您使用现有的基础设施轻松地实现它。

并发模型

WebFlux是以无阻塞的思想构建的,因此使用了与springmvc不同的并发编程模型。

springmvc假设线程将被阻塞,并在阻塞实例期间使用一个大的线程池来保持移动。这个更大的线程池使得MVC资源更密集,因为计算机硬件必须同时保持更多的线

WebFlux使用了一个小的线程池,因为它假设您永远不需要通过工作来避免阻塞。这些线程称为事件循环工作线程,数量固定,在传入请求中的循环速度比MVC线程快。这意味着WebFlux更有效地使用计算机资源,因为活动线程总是在工作。

Spring WebFlux Security

WebFlux使用Spring安全性来实现身份验证和授权协议。springsecurity使用WebFilter根据经过身份验证的用户列表认证请求。

@EnableWebFluxSecurity
public class HelloWebFluxSecurityConfig {@Beanpublic MapReactiveUserDetailsService userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("user").password("user").roles("USER").build();return new MapReactiveUserDetailsService(user);}
}

在这里,我们可以看到用户有一个用户名、一个密码和一个或多个roles标签,这些标签允许自定义定访问。类似于SpringBoot Security的 UserDetailsService接口

开始使用 Spring WebFlux

生成项目

spring代码生成器

参考配置

生成后的pom如下

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.1</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.github.webflux.learn</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

开发接口

自定义一个函数路由:将请求path中的占位参数获取作为返回值

/*** @author coding途中*/
@Configuration
public class TestRouter {@Beanpublic RouterFunction<ServerResponse> routeExample() {return RouterFunctions.route(RequestPredicates.GET("/hello/{path}").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), serverRequest -> {String str = serverRequest.pathVariable("path");return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).bodyValue(str).switchIfEmpty(ServerResponse.notFound().build());});}
}

浏览器请求 http://localhost:4990/hello/haha

haha

添加认证


/*** @author coding途中*/@Configuration
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig  {@Beanpublic MapReactiveUserDetailsService userDetailsService() {UserDetails user = User.withDefaultPasswordEncoder().username("user").password("user").roles("USER").build();return new MapReactiveUserDetailsService(user);}@Beanpublic SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {// @formatter:offreturn http.authorizeExchange().pathMatchers( "/hello/**").authenticated().pathMatchers("/hello/login").permitAll().anyExchange().authenticated().and().formLogin().and().logout().and().httpBasic().and().csrf().disable().build();}
}
  • 再次请求接口 浏览器请求 http://localhost:4990/hello/haha 此时浏览重定向到 http://localhost:4990/login

登陆页面

输入user/user 用户名密码后完成登陆。

再次浏览器请求 http://localhost:4990/hello/authenticate

authenticate

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

新一代Spring Web框架WebFlux!相关推荐

  1. Spring Web框架与Struts的区别

    Spring Web框架与Struts的区别 Posted on 2007-12-25 02:23 Asktalk 阅读(1848) 评论(0) 编辑 收藏 所属分类: Jdbc/Hibernate3 ...

  2. 艿艿连肝了几个周末,写了一篇贼长的 Spring 响应式 Web 框架 WebFlux!市面第二完整~

    本文在提供完整代码示例,可见 https://github.com/YunaiV/SpringBoot-Labs 的 lab-27 目录. 原创不易,给点个 Star 嘿,一起冲鸭! 1. 概述 友情 ...

  3. 艿艿连肝了几个周末,写了一篇贼长的 Spring 响应式 Web 框架 WebFlux!市面第二完整~...

    点击上方"芋道源码",选择"设为星标" 做积极的人,而不是积极废人! 源码精品专栏 原创 | Java 2020 超神之路,很肝~ 中文详细注释的开源项目 RP ...

  4. 第八章 使用Spring Web Flow

    Spring Web Flow是一个Web框架,它适用于元素按规定流程运行的程序.在本章中,我们将会探索Spring Web Flow并了解它如何应用于Spring Web框架平台. Spring W ...

  5. Spring 5 新增全新的reactive web框架:webflux

    Spring 5发布了一个非常重要的模块,名字叫做:spring-webflux.该模块平级的就是spring-webmvc. 具体能做什么呢?自然是mvc不擅长的事情了.自然是人们一直希望实现,但总 ...

  6. 【Spring 5】响应式Web框架实战(下)

    - [Spring 5]响应式Web框架前瞻  - 响应式编程总览  - [Spring 5]响应式Web框架实战(上) 1 回顾 上篇介绍了如何使用Spring MVC注解实现一个响应式Web应用( ...

  7. 【Spring 5】响应式Web框架实战(上)

    前情概要:  - [Spring 5]响应式Web框架前瞻  - 响应式编程总览 1 回顾 通过前两篇的介绍,相信你对响应式编程和Spring 5已经有了一个初步的了解.下面我将以一个简单的Sprin ...

  8. 【Spring 5】响应式Web框架前瞻

    1 响应式宣言 和敏捷宣言一样,说起响应式编程,必先提到响应式宣言. We want systems that are Responsive, Resilient, Elastic and Messa ...

  9. Spring Boot 集成 WebFlux 开发 Reactive Web 应用

    Spring Boot 集成 WebFlux 开发 Reactive Web 应用 <Spring Boot 实战开发>-- 基于 Gradle + Kotlin的企业级应用开发最佳实践 ...

最新文章

  1. 年度书单盘点 | “裁员潮”持续蔓延?职场没有铁饭碗,只有硬技能
  2. Hadoop多次format格式化会导致节点的clusterID不一致
  3. 看看老外是如何理解抽象类的
  4. linspace函数matlab_从零开始的matlab学习笔记——(29)泰勒逼近函数
  5. bootstrap1
  6. 搭建Android开发环境 第二章
  7. Git的使用教程(一)
  8. 医学图像之DICOM格式解析
  9. ee er_61对词根相同后缀分别是er和ee的单词要这样区别记忆它们
  10. 3D MAX插件大全介绍
  11. 1096 大美数 分数 15
  12. STM32定时器的定时时间设置
  13. 搭建前后端分离主流项目完整步骤——在线教育系统(阿里云服务器部署上线)
  14. 艾尔登法环绝配:iGame加推双风扇白色版RTX 3050
  15. 设计用什么笔记本好?三大选择要点
  16. 利用Python实现模糊查找
  17. GSM系统信令接续流程(一)(转)
  18. 上位机与MES对接的常见方式
  19. peda和pwndbg的切换
  20. 商业银行的负债业务,包括活期存款、定期存款和储蓄存款

热门文章

  1. php三级实例,三级联动实例
  2. delay在java中有什么用_java中DelayQueue的使用
  3. php json 美化,php JSON数据格式化(美化)的方法
  4. linux pidof用法,科技常识:Linux pidof命令使用总结
  5. adb native raact 夜神_React Native 与 夜神模拟器的绑定
  6. 深入理解计算机系统 相关课程,深入理解计算机系统
  7. SQL注入:6、SQLMAP的使用
  8. (软件工程复习核心重点)第一章软件工程概论-第四节:软件过程及相关模型
  9. Java编写简单密码问题
  10. VC++调试win32设置参数