Spring WebFlux:响应式编程
在软件开发领域,随着互联网应用的规模和复杂性不断增加,传统的编程模型逐渐暴露出一些局限性,尤其是在面对高并发、大规模数据流处理等场景时。为了应对这些挑战,响应式编程(Reactive Programming)应运而生,它提供了一种更为高效、灵活的编程范式,以适应不断变化的系统需求。
1.Spring WebFlux 简介
WebFlux提供了一个非阻塞、异步的Web框架,允许开发者构建高性能、可伸缩的 Web 应用程序,特别适合处理大量并发连接,如在微服务架构和云环境中。
WebFlux是Spring Framework 5引入的一个重要组件,它代表了Spring对于响应式编程(Reactive Programming)的支持。
1.1.异步非阻塞
异步非阻塞是一种编程模式,它允许程序在等待某个操作完成时继续执行其他任务。这种模式是基于事件循环,可以在单个线程上处理多个I/O操作,大幅提高了系统的吞吐量和伸缩性。
- 服务器端处理: WebFlux 使用 Netty 或 Undertow 这类非阻塞服务器,它们能够处理大量连接而不阻塞线程。这意味着服务器可以在单个线程中同时处理多个请求,提高了资源利用率和吞吐量。
- 数据库访问: 通过使用 R2DBC( Reactive Relational Database Connectivity)或其他支持响应式编程的数据库客户端,可以在数据库查询中实现异步操作,从而避免线程等待数据库响应造成的阻塞。
- 异步API调用: 在处理外部服务调用时,可以利用 WebClient 进行异步HTTP请求,WebClient 是完全非阻塞的,能够在等待响应的同时处理其他任务。
1.2.响应式流(Reactive Streams)
响应式流是一个规范,它定义了异步流处理的接口和行为。
1.2.1.响应式编程
响应式编程是一种编程范式,是一种异步的、事件驱动的编程范式,它特别适用于构建能够处理实时数据流的应用程序。在这种模型中,数据和事件作为流进行处理,允许开发者以声明式的方式构建复杂的异步逻辑。
Spring WebFlux 遵循这一规范,使用 Publisher
作为响应式流的源头,Subscriber
作为流的消费者。这种模型允许开发者以声明式的方式处理异步数据流,同时保持了流的控制和背压管理。
- 数据流处理: Spring WebFlux 集成了 Reactor 库,使用 Flux 和 Mono 类型来处理数据流。Flux 表示0到N个元素的异步序列,Mono 表示0或1个元素的异步结果,两者都支持背压策略,能够智能调整数据生产速度以匹配消费者的处理能力。
- 事件驱动编程: 应用程序可以轻松地处理各种事件,如消息队列中的消息、WebSocket连接上的事件等,通过响应式流模型,可以高效地处理这些事件而不会阻塞主线程。
1.2.2.背压管理
背压是响应式流中的一个重要概念,用于控制生产者和消费者之间的数据流速率。Project Reactor 提供了多种背压策略,帮助开发者处理数据流过载的问题。
1.2.3.Project Reactor
Project Reactor 用于创建和操作响应式流的一组丰富的API。
Project Reactor 是一个基于Java 8的响应式编程库,由Pivotal团队开发,专为配合Spring框架使用而设计。
响应式类型:Mono 和 Flux
- Mono:代表0到1个元素的响应式类型,适合表示单个结果或无结果的异步操作。
- Flux:代表0到N个元素的响应式类型,用于表示多个结果的异步序列。
操作符与响应式数据流
Project Reactor 提供了大量操作符,用于处理响应式流中的元素。这些操作符包括:
- Map:将流中的每个元素应用一个函数,并发布结果。
- Filter:根据条件过滤流中的元素。
- FlatMap:将流中的每个元素转换为另一个流,并将结果流合并为一个流。
- SwitchIfEmpty:如果源流为空,则切换到备选的Mono或Flux。
1.2.4.与传统Spring MVC的比较
Spring MVC是一个基于Servlet API的Web框架,它采用阻塞I/O模型,每个请求/响应对都与一个线程绑定。这在并发量较低时表现良好,但在高并发场景下,线程资源的消耗会急剧增加。
相比之下,Spring WebFlux基于响应式流,它不依赖于Servlet API,可以在如Netty、Undertow等非Servlet服务器上运行。这种模型使得WebFlux能够以非阻塞的方式处理并发请求,有效利用资源,提高性能。
1.3.函数式编程
函数式编程是一种编程范式 , 它强调的是将任务看作一系列可组合的函数调用。通过声明式的方式定义处理流程,让代码更简洁、易读,也更适合处理复杂的异步逻辑。WebFlux采用函数式编程范式,利用Lambda表达式简化了编程模型,路由和请求处理采用函数式编程的方式进行定义,这与传统的基于注解的控制器方法截然不同。
- 路由与处理: WebFlux 提供了函数式编程模型,允许开发者使用 Java 8 的 Lambda 表达式来定义路由规则和处理函数,使得代码更为简洁、可读性强。例如,可以使用
RouterFunctions.route()
方法来定义路由,使用ServerResponse
来构建响应。 - 链式操作与组合: 利用响应式类型 Flux 和 Mono 的丰富操作符,如
map()
,filter()
,flatMap()
等,可以轻松地构建复杂的异步数据处理流程,而无需显式管理回调或线程。
1.3.1.请求路由
使用 RouterFunction
定义请求路由
RouterFunction
是Spring WebFlux中用于定义请求路由的函数接口。通过实现 RouterFunction
,可以精确控制请求的匹配和处理。
路由谓语和处理器
-
路由谓语:用于匹配HTTP请求的特定属性,如路径、方法、头部等。
-
处理器:一旦路由谓语匹配成功,处理器将负责处理请求并返回响应。
1.3.2.函数式端点
Spring WebFlux 还引入了函数式端点的概念,允许开发者以简单的函数形式处理请求和生成响应,这些函数通常返回 ServerResponse
。
1.3.3.函数式编程与响应式流
函数式编程是响应式编程模型的一个重要组成部分。它提倡使用无副作用的函数、不可变数据结构,并且推崇声明式编程。这些原则与响应式流的概念相契合,响应式流强调数据流的声明式处理,以及在数据流中应用各种操作符来转换、过滤和组合数据。
2.Spring WebFlux 应用搭建
2.1 环境准备
项目依赖配置
基于Maven的Springboot项目,pom.xml
文件中的依赖配置可能如下所示:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
2.2 定义路由与处理器
2.2.1.创建 RouterFunction
Bean
在Spring WebFlux中,使用RouterFunction
来定义请求的路由。
首先,创建一个配置类,并在其中定义路由:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;@Configuration
public class WebFluxConfig {@Beanpublic RouterFunction<ServerResponse> route(MyHandler handler) {return RouterFunctions.route(GET("/hello"), handler::hello);}
}
RouterFunctions.route()
是用来创建路由规则的起点。GET("/hello")
是来自RequestPredicates
的静态方法,定义了一个谓词,用于匹配HTTP GET方法并且路径为"/hello"的请求。handler::hello
是一个方法引用,指向MyHandler
类中名为hello
的方法。这意味着当匹配到上述HTTP请求条件时,会调用handler.hello()
方法来处理请求,并期待它返回一个ServerResponse
对象作为响应。
2.2.2.使用 HandlerFunction
处理请求
创建一个处理器类,它将包含处理请求的方法。这些方法可以返回Mono<ServerResponse>
或Flux<ServerResponse>
,这取决于它们处理的是单个响应还是响应流:
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;@Component
public class MyHandler {public Mono<ServerResponse> hello(ServerRequest request) {String name = request.queryParam("name").orElse("World");String message = "Hello, " + name + "!";return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).body(Mono.just(message), String.class);}
}
hello
的方法,用于处理HTTP请求并返回一个响应。
-
Mono<ServerResponse>
:返回类型是一个Mono
对象,这是Reactor库中的一个类,用于表示0到1个元素的异步序列。在这里,它最终会包含一个ServerResponse
对象,即HTTP响应。使用Mono
是为了支持非阻塞和响应式编程。 -
ServerRequest request
:输入参数,表示接收到的HTTP请求信息。
提取查询参数:
String name = request.queryParam("name").orElse("World");
这行代码从ServerRequest
对象中尝试获取名为"name"的查询参数。如果请求中包含了这个参数,比如http://example.com/hello?name=John
,那么name
变量就会被赋值为"John";如果没有提供,则使用默认值"World"。
构建HTTP响应的:
ServerResponse.ok()
:创建一个表示成功(HTTP状态码200 OK)的基础响应。.contentType(MediaType.TEXT_PLAIN)
:设置响应的内容类型为纯文本(PLAIN TEXT)。.body(Mono.just(message), String.class)
:指定响应体的内容。这里使用Mono.just(message)
来包装问候消息字符串,表明响应体是一个包含单个元素的异步序列,其类型为String
。这确保了整个处理过程是非阻塞的。
2.3 全局异常处理
全局异常处理是任何Web应用程序的重要部分
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {public Mono<Void> handleException(ServerWebExchange exchange, Throwable ex) {ServerHttpResponse response = exchange.getResponse();response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);DataBuffer buffer = response.bufferFactory().wrap("{\"error\": \"Internal Server Error\"}".getBytes());return response.writeWith(Mono.just(buffer));}
}
这段代码定义了一个全局异常处理器,用于在Spring WebFlux应用中捕获未处理的异常,并向客户端返回统一的错误响应。下面是各部分的详细说明:
@Order(Ordered.HIGHEST_PRECEDENCE)
@Order
注解用于指定组件的执行顺序,特别是当有多个相同类型的组件(比如多个异常处理器)需要按特定顺序执行时。Ordered.HIGHEST_PRECEDENCE
是一个常量,值为Integer.MIN_VALUE
,意味着这个异常处理器将具有最高优先级,会在所有其他相同类型的组件之前执行。换句话说,如果有其他异常处理器也能够处理相同的异常类型,但没有指定这么高的优先级,那么这个GlobalExceptionHandler
将会优先处理异常。
handleException
方法
-
参数:
ServerWebExchange exchange
:封装了HTTP请求和响应的所有信息,是WebFlux处理请求时的核心对象。Throwable ex
:抛出的异常对象,即需要被处理的异常。
-
功能:
-
设置响应状态码:首先通过
exchange.getResponse()
获取响应对象,并将其状态码设置为HttpStatus.INTERNAL_SERVER_ERROR
(HTTP 500),表示服务器遇到了未知错误。 -
构建响应体:使用
response.bufferFactory().wrap()
方法创建一个包含错误信息的DataBuffer
对象。这里的消息体内容为{"error": "Internal Server Error"}
,表示发生了内部服务器错误。 -
写入响应体并完成响应:最后,通过
response.writeWith(Mono.just(buffer))
将构建好的错误信息缓冲区写入响应,并返回一个Mono<Void>
表示这是一个无返回值的异步操作,完成响应的发送。
-
3.应用细节
3.1.RouterFunction
RouterFunction
是 Spring WebFlux 提供的一种用于定义和处理 HTTP 路由的功能性接口,它是构建响应式 Web 应用的基础组件之一。与传统的基于注解(如 @GetMapping
, @PostMapping
等)的控制器相比,RouterFunction
提供了一种更为灵活和强大的方式来定义路由逻辑,特别适合函数式编程风格。下面详细介绍其用法:
3.1.1. 基本概念
- RouterFunction: 表示一个路由处理逻辑,它将HTTP请求与相应的处理逻辑关联起来。一个
RouterFunction
可以匹配一个请求,返回下一个RouterFunction
或一个处理结果(HandlerFunction
)。 - HandlerFunction: 表示一个处理逻辑,它接受一个
ServerRequest
并返回一个Mono<ServerResponse>
。即,它负责处理请求并产生响应。
3.1.2. 创建 RouterFunction
创建 RouterFunction
通常涉及定义路由规则和处理逻辑。以下是一个简单的示例,展示如何定义一个路由来处理 GET 请求:
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 static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@Configuration
public class GreetingRouter {@Beanpublic RouterFunction<ServerResponse> routingFunction() {return route(GET("/hello2"), request -> ok().bodyValue("Hello, Spring WebFlux!"));}
}
这段代码定义了一个简单的RouterFunction<ServerResponse>
实例,用于处理一个HTTP GET请求到/hello
路径的路由逻辑。
return route(GET("/hello"), request -> ok().bodyValue("Hello, Spring WebFlux!"));
-
route
是RouterFunctions
类中的一个静态方法,用于创建一个基础的路由定义。它接受两个参数:- 谓词(Predicate):
GET("/hello")
是一个谓词,用于匹配HTTP请求的方法和路径。这里匹配所有GET方法并且路径为/hello
的请求。 - 处理器函数(HandlerFunction):
request -> ok().bodyValue("Hello, Spring WebFlux!")
是一个Lambda表达式,代表了处理请求的具体逻辑。它接受一个ServerRequest
对象作为输入,并返回一个Mono<ServerResponse>
表示响应结果。
- 谓词(Predicate):
-
request -> ...
:Lambda表达式定义了如何根据请求生成响应。 -
ok()
:这是ServerResponse
的静态工厂方法,用于创建一个表示成功(HTTP状态码200 OK)的响应。 -
.bodyValue("Hello, Spring WebFlux!")
:此方法设置了响应体的内容为给定的字符串,并且指定了内容类型(默认情况下,如果没有显式指定,会根据内容推断)。
3.1.3. 注册 RouterFunction
要在 Spring Boot 应用中注册 RouterFunction
,通常在配置类中声明为 @Bean
,以便 Spring 自动发现并配置:
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.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;@Configuration
public class WebConfig {@Beanpublic RouterFunction<ServerResponse> routes(GreetingHandler handler) {return RouterFunctions.route(GET("/hello"), handler::sayHello);}
}
其中,GreetingHandler
是一个包含业务逻辑的 HandlerFunction
。
3.1.4. 组合 RouterFunction
RouterFunction
可以组合起来形成复杂的路由结构。这使得路由配置更加模块化和可维护。
- 谓词组合:可以使用
and
,or
等逻辑运算符组合谓词,以实现更复杂的路由匹配逻辑。
@Beanpublic RouterFunction<ServerResponse> route(MyHandler handler) {return RouterFunctions.route(GET("/haha").and(accept(MediaType.TEXT_PLAIN)), handler::haha).andRoute(GET("/reactor").and(accept(MediaType.APPLICATION_JSON)), handler::reactorExample);}
// 处理简单的文本请求public Mono<ServerResponse> haha(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.TEXT_PLAIN).bodyValue("haha!");}// 处理复杂的响应式请求public Mono<ServerResponse> reactorExample(ServerRequest request) {return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).bodyValue("{'message': 'This is a reactive response!'}");}
3.1.5. 处理请求参数
RouterFunction
可以方便地处理请求参数,无论是路径参数、查询参数还是请求体。例如,处理带路径参数的请求:
public RouterFunction<ServerResponse> userRoutes(UserHandler handler) {return route(GET("/users/{id}"), handler::getUserById);
}
3.1.6. 错误处理
Spring WebFlux 支持全局或局部的错误处理,可以通过提供一个处理特定异常类型的 RouterFunction
实现:
3.1.6.1.自带异常处理
@Beanpublic RouterFunction<ServerResponse> route(MyHandler handler) {return RouterFunctions.route().GET("/greeting", handler::reactorExample)// 全局错误处理.onError(Exception.class, (exception, request) -> {System.out.println("An exception occurred: " + exception.getMessage());return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.TEXT_PLAIN).bodyValue("Oops! Something went wrong.");}).build();}
3.1.6.2.全局异常处理
// 错误处理路由private RouterFunction<ServerResponse> errorRoute() {return RouterFunctions.route(RequestPredicates.all(),request -> ServerResponse.status(HttpStatus.BAD_REQUEST).contentType(MediaType.TEXT_PLAIN).bodyValue("这是一条用于未匹配请求的回退处理。"));}
RequestPredicates.all()
,意味着这个谓词将匹配所有的HTTP请求,无论方法(GET、POST等)或路径是什么。
3.1.7. 高级用法
- 过滤器与拦截器:可以插入自定义的过滤器或拦截器来处理请求的预处理或后处理逻辑。
- 条件路由:基于环境、配置或其他条件动态选择路由。
3.2.请求接参
Spring WebFlux 完全支持接收 RESTful 风格的传参。RESTful 风格的接口通常通过URL路径、查询参数、请求头以及请求体来传递参数。在Spring WebFlux中,你可以使用函数式和注解两种方式来定义端点以接收这些参数。下面是几种常见的接收参数方式:
3.2.1. 路径变量(Path Variables)
在路由定义中,你可以使用 {variableName}
来标记路径变量,然后在处理器方法中通过 @PathVariable
注解接收它们。
@Bean
public RouterFunction<ServerResponse> userRoute(UserHandler handler) {return RouterFunctions.route(GET("/users/{id}"), handler::getUserById);
}@Component
public class UserHandler {public Mono<ServerResponse> getUserById(ServerRequest request) {String id = request.pathVariable("id");// 处理逻辑...}
}
3.2.2. 查询参数(Query Parameters)
查询参数可以直接从 ServerRequest
中获取,或者使用 @RequestParam
注解。
public Mono<ServerResponse> searchUsers(ServerRequest request) {String keyword = request.queryParam("keyword").orElse("");// 处理逻辑...
}
或者使用 @RequestParam
:
public Mono<ServerResponse> searchUsers(@RequestParam(name = "keyword", defaultValue = "") String keyword) {// 处理逻辑...
}
3.2.3. 请求体(Request Body)
对于POST、PUT等方法,你可能需要从请求体中读取数据。可以使用 @RequestBody
注解,并指定相应的对象类型来自动绑定JSON或表单数据。
public Mono<ServerResponse> createUser(@RequestBody UserDTO user) {// 处理逻辑...
}
3.2.4. 请求头(Request Headers)
请求头可以通过 ServerRequest.headers()
获取,或者使用 @RequestHeader
注解。
public Mono<ServerResponse> handleRequestWithHeader(@RequestHeader("Authorization") String authHeader) {// 处理逻辑...
}
3.3.响应内容
3.3.1.ServerResponse
对象
在Spring WebFlux中,响应的返回内容通常是通过ServerResponse
对象来构建和管理的,它代表了即将发送给客户端的HTTP响应。
3.3.1.1. 状态码(Status Code)
每个HTTP响应都会有一个状态码,用于表示请求的处理结果。在Spring WebFlux中,你可以通过如下方式设置状态码:
- 使用静态方法,如
ServerResponse.ok()
表示200 OK,ServerResponse.created()
表示201 Created等。 - 或者直接指定状态码,如
ServerResponse.status(HttpStatus.NOT_FOUND)
表示404 Not Found。
3.3.1.2. 响应体(Body)
响应体是响应的主要内容,可以是文本、JSON、XML等各种格式的数据。构建响应体的方式有多种:
- 直接值:使用
.bodyValue("响应内容")
,适合于简单的字符串或对象直接转换为响应体。 - 流(Stream):
.body(fromPublisher(publisher, MediaType))
,当响应内容来自一个publisher(如Flux或Mono)时,这种方式非常适合处理异步数据流。 - 对象转换:结合
.body(toEntity(object))
或.bodyValue(object)
结合Jackson等库自动将Java对象转换为JSON等格式的响应体。
3.3.1.3. 内容类型(Content-Type)
通过.contentType(MediaType)
方法指定响应的内容类型,如MediaType.APPLICATION_JSON_UTF8
表示JSON格式,MediaType.TEXT_PLAIN
表示纯文本等。
3.3.1.4. 头部(Headers)
可以在响应中添加自定义头部信息,如.header("X-Custom-Header", "value")
。
3.3.1.5. 构建响应
一旦定义好状态码、响应体、内容类型和头部等信息,通过.build()
方法完成ServerResponse
的构建。这一步是必要的,它将之前设置的所有配置整合成一个待发送的响应对象。
3.3.1.6.示例
假设我们要构建一个返回JSON格式数据的响应:
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;public Mono<ServerResponse> getUserInfo(ServerRequest request) {// 假设getUserDetails()是一个Mono<User>,User是自定义的Java对象Mono<User> userDetails = userService.getUserDetails(request.pathVariable("id"));return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(userDetails, User.class) // 自动转换User对象为JSON.switchIfEmpty(ServerResponse.notFound().build()); // 如果找不到用户,返回404
}
在这个例子中,我们首先指定了响应的状态码为200 OK,内容类型为JSON,然后将从数据库查询到的用户详情(User
对象)转换为JSON响应体。如果查询结果为空(即用户不存在),则通过.switchIfEmpty()
方法切换到返回404 Not Found的响应。
3.3.2.响应式类型
在响应式编程领域,尤其是在使用Spring WebFlux和Reactor框架时,Mono
和Flux
是两个核心的响应式类型,它们都是Reactor库提供的对Reactive Streams规范的实现。这两种类型都实现了Publisher
接口,这意味着它们可以作为异步数据流的生产者,在响应式系统中扮演着至关重要的角色。
3.3.2.1. Mono
-
概念:
Mono
代表0或1个元素的异步序列。换句话说,它要么发出一个元素,要么发出一个完成信号(表示没有元素),或者发出一个错误信号。这使得它非常适合用于表示单个结果或者空结果的场景,比如数据库查询返回单行记录,或者执行一个可能会失败的操作。 -
典型用途:数据库查询的单一结果、网络请求的响应、计算单一值的操作等。
-
操作符:
Mono
提供了丰富的操作符,如map
、flatMap
、zip
、then
等,用于处理单个数据流的变换、组合和错误处理。1. map
-
功能:对
Mono
中的元素应用一个函数进行转换。Mono<String> monoStr = Mono.just("Hello"); Mono<Integer> monoLength = monoStr.map(String::length);
2. flatMap
-
功能:将
Mono
中的元素转换为另一个Mono
或Flux
,然后扁平化这个结果流,使其成为单一流。Mono<User> monoUser = ...; // 假设获取用户信息 Mono<Address> monoAddress = monoUser.flatMap(user -> getAddressForUser(user.getId()));
3. then
-
功能:忽略
Mono
中的元素,仅当前Mono
完成时,执行下一个Mono
。Mono<Void> saveUser = userRepository.save(newUser); Mono<User> findUser = saveUser.then(userRepository.findById(newUser.getId()));
4. zipWith
-
功能:将两个
Mono
的输出按照某种方式结合(通常是元组),只有当两个Mono
都成功时才会触发。Mono<User> monoUser = ...; Mono<Order> monoOrder = ...; Mono<Tuple2<User, Order>> combined = monoUser.zipWith(monoOrder);
-
3.3.2.2. Flux
-
概念:
Flux
代表0到N个元素的异步序列。它可以发出任意数量的元素,包括无限数量,直到它完成或者遇到错误。这使得Flux
非常适合处理集合、事件流或者任何可能有多次数据推送的场景。 -
典型用途:处理列表或集合的数据、实时数据流(如WebSocket消息)、数据库查询的多行结果等。
-
操作符:
Flux
同样提供了丰富的操作符,如map
、filter
、concatMap
、buffer
、window
等,用于处理数据流的变换、过滤、合并、窗口化等操作。1. map
-
功能:对流中的每个元素应用一个函数进行转换。
Flux<String> names = Flux.fromIterable(Arrays.asList("Alice", "Bob", "Charlie")); Flux<Integer> lengths = names.map(String::length);
2. filter
-
功能:根据条件从流中筛选元素。
Flux<String> names = ...; Flux<String> longNames = names.filter(name -> name.length() > 5);
3. flatMap
-
功能:将流中的每个元素转换为一个新的流,然后将这些流合并成一个单一的流。
Flux<User> users = ...; Flux<Order> orders = users.flatMap(user -> getOrderListForUser(user.getId()));
4. buffer
-
功能:将流中的元素收集到缓冲区中,达到一定条件(如数量或时间)后作为一个列表或数组发出。
Flux<Integer> numbers = Flux.interval(Duration.ofMillis(100)).take(10); Flux<List<Integer>> buffered = numbers.buffer(3); // 每3个元素打包一次
5. concatMap
-
功能:类似于
flatMap
,但保证按源流的顺序依次处理每个元素产生的流。Flux<User> users = ...; Flux<Order> ordersSequential = users.concatMap(user -> getOrderListForUser(user.getId()));
-
3.3.3.3.共同特点
-
响应式:
Mono
和Flux
都是非阻塞的,支持背压(Backpressure),能够在数据生产者和消费者之间自动调节数据流动的速度,防止生产过快导致消费端处理不过来的情况。 -
链式操作:两者都支持链式调用操作符,允许以声明式的方式构建复杂的异步数据处理流程,而无需显式地管理线程或回调。
-
异步和事件驱动:它们的设计哲学鼓励编写异步和事件驱动的代码,提高了系统的可伸缩性和资源利用率。
3.3.3.4.转换关系
Mono
和Flux
之间可以通过操作符互相转换,例如,Mono
可以通过flatMapMany
转换为Flux
,而多个Mono
或Flux
可以通过concat
、merge
等操作合并。
4.WebClient
WebClient
是Spring Framework 5引入的一个非阻塞、响应式的HTTP客户端,它属于Spring WebFlux模块的一部分。它设计用于构建高性能、异步的Web服务客户端,特别适合用于处理大量的并发请求和与响应式服务交互。下面是对WebClient
的详细讲解:
核心特性
-
非阻塞:
WebClient
基于非阻塞IO,这意味着它在等待数据时不会占用线程,从而能够高效地处理大量并发连接,减少资源消耗。 -
响应式:遵循Reactor项目中的响应式编程模型,使用
Mono
和Flux
作为返回类型,便于处理异步数据流和背压机制。 -
链式调用:提供了一套流畅的API,允许通过链式调用来构建HTTP请求,易于理解和维护。
-
配置灵活:支持多种配置选项,包括基础URL、默认头、超时设置、SSL配置等。
-
内容协商:自动处理内容类型和编码,支持JSON、XML等多种数据格式的序列化和反序列化。
-
过滤器:允许添加自定义过滤器来修改请求或响应,实现日志记录、认证、重试逻辑等。
基本使用
创建WebClient
实例
// 最简单的创建方式
WebClient client = WebClient.create();// 配置化创建
WebClient client = WebClient.builder().baseUrl("http://example.com").defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).build();
发送GET请求
Mono<String> response = client.get().uri("/api/data").retrieve() // 获取响应体.bodyToMono(String.class); // 将响应体转换为String类型
发送POST请求
Mono<String> response = client.post().uri("/api/data").body(BodyInserters.fromObject(someObject)) // 发送对象.retrieve().bodyToMono(String.class);
处理响应
- 使用
.block()
方法会阻塞当前线程直到响应到来,这通常只在测试或者非响应式环境中使用。 - 在响应式环境中,应该通过订阅
Mono
或Flux
来处理响应,或者将其与其它响应式流进行组合。
错误处理
可以使用.onErrorResume
等操作符来优雅地处理错误情况,例如重试逻辑或返回默认值。
Mono<String> withErrorHandling = client.get().uri("/api/data").retrieve().onErrorResume(WebClientResponseException.class, e -> {// 处理错误,比如返回默认值return Mono.just("Default Value");}).bodyToMono(String.class);
总结
WebClient
是一个强大且灵活的工具,它在现代Web应用和服务间通信中扮演着重要角色,特别是当需要构建高性能、可扩展的系统时。通过充分利用响应式编程的优势,开发者可以构建出更加高效、易维护的客户端逻辑。
相关文章:

Spring WebFlux:响应式编程
在软件开发领域,随着互联网应用的规模和复杂性不断增加,传统的编程模型逐渐暴露出一些局限性,尤其是在面对高并发、大规模数据流处理等场景时。为了应对这些挑战,响应式编程(Reactive Programming)应运而生…...

uniapp、web网页跨站数据交互及通讯
来来来,说说你的创作灵感!这就跟吃饭睡觉一样,饿了就找吃的,渴了就倒水张口灌。 最近一个多月实在是忙的没再更新日志,好多粉丝私信说之前的创作于他们而言非常有用!受益菲浅,这里非常感谢粉丝…...

2024-05-10 Ubuntu上面使用libyuv,用于转换、缩放、旋转和其他操作YUV图像数据,测试实例使用I420ToRGB24
一、简介:libyuv 最初是由Google开发的,主要是为了支持WebRTC项目中的视频处理需求。用于处理YUV格式图像数据的开源库。它提供了一系列的函数,用于转换、缩放、旋转和其他操作YUV图像数据。 二、执行下面的命令下载和安装libyuv。 git clo…...
怎么给视频加水印?2招轻松搞定
在数字媒体时代,视频水印作为一种有效的版权保护手段,被广泛应用于各种场景。给视频添加水印不仅可以防止内容被恶意盗用,还能增加视频的辨识度,提升品牌形象。本文将为您介绍2种简单易行的方法,教您怎么给视频加水印&…...

SpringBootWeb 篇-深入了解请求响应(服务端接收不同类型的请求参数的方式)
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 请求响应概述 1.1 简单参数 1.2 实体参数 2.3 数组集合参数 2.4 日期参数 2.5 json 参数 2.6 路径参数 3.0 完整代码 1.0 请求响应概述 当客户端发送不同的请求参…...

实验十 智能手机互联网程序设计(微信程序方向)实验报告
实验目的和要求 完成以下页面设计。 二、实验步骤与结果(给出对应的代码或运行结果截图) Wxml <view class"container"> <view class"header"> <view class"logo"…...

Python图形复刻——绘制母亲节花束
各位小伙伴,好久不见,今天学习用Python绘制花束。 有一种爱,不求回报,有一种情,无私奉献,这就是母爱。祝天下妈妈节日快乐,幸福永远! 图形展示: 代码展示: …...

【算法优选】 动态规划之子数组、子串系列——壹
文章目录 🎋前言🎋最大子数组和🚩题目描述🚩算法思路🚩代码实现 🌴环形子数组的最大和🚩题目描述🚩算法思路:🚩代码实现 🌲乘积最大子数组&#x…...

PXE+Kickstart无人值守安装安装Centos7.9
文章目录 一、什么是PXE1、简介2、工作模式3、工作流程 二、什么是Kickstart1、简介2、触发方式 三、无人值守安装系统工作流程四、实验部署1、环境准备2、服务端:关闭防火墙和selinux3、添加一张仅主机的网卡4、配置仅主机的网卡4.1、修改网络连接名4.2、配IP地址4…...
C语言实现通讯录,包括增删改查以及动态开辟内存,写入文件等功能
文章目录 前言一、注意二、源码1. test.c源文件2. contact.h头文件3. contact.c源文件 总结 前言 C语言实现通讯录,包括增删改查以及动态开辟内存,写入文件等功能 一、注意 在通讯录菜单栏使用枚举定义PeoInfo类型时,每个结构体类型的成员…...

flowable多对并发网关跳转的分析
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://218.75.87.38:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码: h…...
【嵌入式——QT】QT集成Ymodem协议使用UDP进行传输
【嵌入式——QT】QT集成Ymodem协议使用UDP进行传输 Ymodem协议帧的数据格式帧头包号校验 通讯过程握手信号起始帧数据帧结束帧代码块 Ymodem命令 QT实现YmodemFileTransmit.hYmodemFileTransmit.cppBootLoader.hBootLoader.cppYmodem协议源码 Ymodem协议 帧的数据格式 帧头、…...
python笔记(17)输入输出
一、标准输入与输出简介 Python通过内置的sys模块管理标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。但对大多数简单应用而言,直接使用内置函数就足够了。 二、输入:inpu…...
408数据结构总结复习笔记一:线性表
408数据结构总结复习笔记一:线性表 从现在开始慢慢更新我的考研复习笔记系列吧~ PS:主要是我自己个人复习过程中觉得重点的点,大家仅供参考哈~ 上岸!!!大家一起加油! 顺序表和链表的比较 顺序表链表存取…...
Docker——目录迁移
我们在生产环境中安装Docker时,默认的安装目录是/var/lib/docker,而通常情况下,规划给系统盘的目录一般为50G,该目录是比较小的,一旦容器过多或容器日志过多,就可能出现Docker无法运行的情况,所…...

SpringAMQP-消息转换器
这边发送消息接收消息默认是jdk的序列化方式,发送到服务器是以字节码的形式,我们看不懂也很占内存,所以我们要手动设置一下 我这边设置成json的序列化方式,注意发送方和接收方的序列化方式要保持一致 不然回报错。 引入依赖&#…...

轻松拿下指针(5)
文章目录 一、回调函数是什么二、qsort使用举例三、qsort函数的模拟实现 一、回调函数是什么 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数 时&#x…...

Nginx反向代理配置
一、介绍 Nginx 的反向代理功能在现代网络架构中扮演着至关重要的角色。首先,它充当了客户端与后端服务器之间的中介。当客户端发送请求时,这些请求先到达 Nginx 服务器,Nginx 会根据预先设定的规则和配置,将请求准确地转发到相应…...

突破编程界限:探索AI编程新境界
文章目录 一、AI编程助手1.1 Baidu Comate智能代码助手1.2 阿里云 通义灵码 二、场景需求三、体验步骤3.1 官网下载3.2 手动下载 四、试用感受4.1 提示4.2 注释生成代码4.3 代码生成4.4 选中生成注释4.5 查看变更&新建文件4.6 调优建议4.7 插件使用 五、结尾推荐 一、AI编程…...

C语言(指针)2
Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,关注收藏,欢迎欢迎~~ 💥个人主页:小羊在奋斗 💥所属专栏:C语言 本系列文章为个人学习笔记&#x…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...