反应式系统提供了我们在高数据流世界中所需的无与伦比的响应能力和可扩展性。然而,反应式系统需要经过专门培训的工具和开发人员来实现这些独特的程序架构。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 中添加,并增加了对以下内容的支持:
最终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();
- 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);
- 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);
- 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有四个主要接口:
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 {
- @Bean
- public 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 {
- @Bean
- public 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
- /**
- * @author coding途中
- */
- @Configuration
- @EnableWebFluxSecurity
- public class HelloWebfluxSecurityConfig {
- @Bean
- public MapReactiveUserDetailsService userDetailsService() {
- UserDetails user = User.withDefaultPasswordEncoder()
- .username("user")
- .password("user")
- .roles("USER")
- .build();
- return new MapReactiveUserDetailsService(user);
- }
- @Bean
- public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
- // @formatter:off
- return http.authorizeExchange()
- .pathMatchers( "/hello/**").authenticated()
- .pathMatchers("/hello/login").permitAll()
- .anyExchange().authenticated()
- .and()
- .formLogin().and()
- .logout().and()
- .httpBasic().and()
- .csrf().disable()
- .build();
- }
- }
输入user/user 用户名密码后完成登陆。
再次浏览器请求 http://localhost:4990/hello/authenticate
- authenticate