WebFlux学习笔记
WebFlux基础
WebFlux简介
官网介绍
- 【原文】Spring WebFlux is a non-blocking web framework built from the ground up(自底向上) to take advantage of(利用) multi-core(多核),next-generation processors(下一代处理器) and handle massive(大量) numbers of concurrent connections(并发连接).
- 【翻译】Spring WebFlux 是一个自底向上构建的非阻塞 Web 框架,用于利用多核、下一代处理器处理高并发连接。
官方文档
打开 Spring 官网,可以看到 WebFlux 在线参考文档入口地址。
- 【原文】The original(原始的) web framework included in the Spring Framework, Spring Web MVC, was purpose built for(专为构建)* the Servlet API and Servlet containers. The reactive-stack web framework, Spring WebFlux, was added later in version 5.0. It is fully non-blocking, supports Reactive Streams(反应式流) back pressure(背压), and runs on servers such as Netty, Undertow, and Servlet 3.1+ containers.
- 【翻译】原始的 web 框架包含在 Spring 框架中,即 Spring Web MVC,是专为 Servlet API 与 Servlet 容器构建的。Reactive-stack web 框架,即 Spring WebFlux,最近被添加到了 Spring 5.0 版本中。它是完全地非阻塞的,支持 Reactive Streams 背压,运行在诸如 Netty、Undertow 与 Server3.1+容器中。
- 【原文】Both web frameworks mirror(反映) the names of their source modules(源模块) spring-webmvc and spring-webflux and co-exist(共存) side by side(一起) in the Spring Framework. Each module is optional. Applications may use one or the other module, or in some cases both — e.g.(例如,发音与意义与 For example 相同) Spring MVC controllers with the reactive WebClient.
- 【翻译】两个 Web 框架都反映了它们源模块的名称:spring-webmvc 与 spring-webflux,并且它们共存于 Spring 框架之中。每一个模块都是可选的。应用程序可以选择一个或另一个模块,或者在某些情况下两个同时使用。例如,SpringMVC 使用反应式 Web 客户端进行控制。
牛刀小试
第一个WebFlux工程 02-webflux-primary
- 创建一个 Spring Boot 工程,命名为 02-webflux-primary,Boot 版本要求最低为 2.0.0。不要添加原来的web 依赖,而是要添加 Reactive Web,即 flux 依赖。
<!-- webflux依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
- 配置文件:
logging:
pattern:
console: level - %level %d %msg%n
- 基本使用:定义接口控制器
@RestController
@RequestMapping("/some")
public class SomeController {
@GetMapping("/common")
public String commonHandle() {
return "common handler";
}
/**
* Mono: 表示包含0或1个元素的异步序列
*/
@GetMapping("/mono")
public Mono<String> monoHandle() {
// 静态方法just()可用于指定该异步序列中所包含的元素
return Mono.just("mono handler");
}
/**
* Flux: 表示包含0个或多个元素的异步序列
*/
@GetMapping("/flux")
public Flux<String> fluxHandle() {
return Flux.just("beijing", "shanghai", "guangzhou", "shenzhen");
}
/**
* 数组转为Flux
*/
@GetMapping("/array")
public Flux<String> arrayHandle(@RequestParam String[] interests) {
return Flux.fromArray(interests);
}
/**
* 集合转为Flux
*/
@GetMapping("/list")
public Flux<String> listHandle(@RequestParam List<String> interests) {
return Flux.fromStream(interests.stream());
}
}
- 小结:WebFlux编程控制器返回对象为 Mono 或者是 Flux。
Mono执行耗时操作
- 计时器:
public class Timer {
private long startTime;
public Timer() {
startTime = System.currentTimeMillis();
}
public long elapsedTime() {
return System.currentTimeMillis() - startTime;
}
}
- 定义接口控制器:
@Slf4j
@RestController
@RequestMapping("/other")
public class OtherController {
@GetMapping("/common")
public String commonHandle() {
Timer timer = new Timer();
String msg = doSome("common msg");
log.info("耗时 {} ms", timer.elapsedTime());
return msg;
}
/**
* Mono: 表示包含0或1个元素的异步序列
*/
@GetMapping("/mono")
public Mono<String> monoHandle() {
Timer timer = new Timer();
Mono<String> monoMsg = Mono.fromSupplier(() -> doSome("mono msg"));
log.info("耗时 {} ms", timer.elapsedTime());
return monoMsg;
}
/**
* 模拟耗时操作
*/
private String doSome(String msg) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception ignore) {}
return msg;
}
}
- 接口测试:分别访问 http://localhost:8080/other/common、http://localhost:8080/other/mono,浏览器都是大约 5 秒给出结果,再看看后台输出却不一样【采用Mono方式的处理耗时操作采用异步非阻塞方式执行,服务端吞吐量提高了。】
Flux 执行耗时操作
- Stream 流中的每个元素将调用一次耗时操作 doSome(),即若 interests 集合中若存在三个元素,则其就会调用三次 doSome()方法。
@GetMapping("/flux")
public Flux<String> fluxHandle(@RequestParam List<String> interests) {
Timer timer = new Timer();
Flux<String> flux = Flux.fromStream(interests.stream().map(s -> doSome("elem-" + s + " ")));
log.info("耗时 {} ms", timer.elapsedTime());
return flux;
}
- 接口测试:http://localhost:8080/other/flux?interests=reading,swimming,running,服务端是非阻塞的,而对于客户端需要等待 15秒 才能一次性获得所有响应结果,并没有提升客户体验。
- SSE:Server-Sent Event,服务端推送事件。
// SSE,Server Sent Event
@GetMapping(value = "/sse", produces = "text/event-stream")
public Flux<String> sseHandle() {
return Flux.just("reading", "swimming", "fitness");
}
@GetMapping(value = "/sse/flux", produces = "text/event-stream")
public Flux<String> sseFluxHandle(@RequestParam List<String> interests) {
Timer timer = new Timer();
Flux<String> flux = Flux.fromStream(interests.stream().map(s -> doSome("elem-" + s + " ")));
log.info("耗时 {} ms", timer.elapsedTime());
return flux;
}
- 接口测试:http://localhost:8080/other/sse/flux?interests=reading,swimming,running
SSE
SSE简介
- 反应式流编程中经常与 SSE 相结合使用,SSE(Server-Sent Event,服务端推送事件),HTML5 规范中的一个组成部分,一个子规范。由于这是官方特性,主流浏览器对其支持是较好的(除了火狐)。
- SSE 与 WebSocket 对比:
- WebSocket:是双工通道;
- SSE:是单工通道,只能是服务端向客户端发送消息。
SSE技术规范
- SSE 规范比较简单,主要由两个部分组成:
- 服务端与浏览器之间的通讯协议
- 浏览器中可供 JavaScript 使用的 EventSource 对象
通讯协议
- 这个通讯协议是基于纯文本的简单协议。服务器端的响应内容类型必须是“text/event-stream”。响应文本的内容是一个事件流,事件流是一个简单的文本流,仅支持 UTF-8 格式的编码。
- 事件流由不同的事件组成。不同事件间通过仅包含回车符和换行符的空行(“\r\n”)来分隔。
- 每个事件可以由多行构成,每行由类型和数据两部分组成。类型与数据通过冒号 (“:”) 进行分隔,冒号前的为类型,冒号后的为其对应的值。每个事件可以包含如下类型的行:
- 类型为 data,表示该行是事件所包含的数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
- 类型为 event,表示该行用来声明事件名称。浏览器在收到数据时,会产生对应名称的事件。
- 类型为空白,表示该行是注释,会在处理时被忽略。
- 类型为 retry,表示浏览器在连接断开之后进行重连的等待时间。
- 类型为 id,表示事件的标识符,标识符用于连接中断后的继连。
- 举例:
data:china // 该事件仅包含数据
data:Beijing
:this is custom event // 注释
event:myevent // 该事件指定了名称
data:shanghai
id:101
retry:3s
EventSource 对象
- 对于服务端发送的带有事件的响应,浏览器需要在 JavaScript 中使用 EventSource 对象进行处理。EventSource 使用的是标准的事件监听器方式(注意,这里的事件并不是响应中所带的事件,而是浏览器上所发生的事件)。当相应的事件发生时,只需使 EventSource 对象调用相应的事件处理方法即可。EventSource 提供了三个标准事件。
事件名称 | 事件触发条件 | 事件处理方法 |
---|---|---|
open | 当浏览器成功与服务端简历连接时 | onopen() |
message | 当收到服务端发送的事件时 | onmessage()、addEventListener() |
error | 当发生异常时触发 | onerror() |
SSE示例演示 03-webflux-sse
- 复制 02-webflux-primary 工程,重命名为 03-webflux-sse,在此基础上修改,导入 web 依赖与 Thymeleaf 依赖。
- 定义处理器接口:
@Controller
public class SomeController {
/**
* 跳转到 default-see.html 页面
*/
@RequestMapping("/default")
public String defaultSSEHandle() {
return "/default-sse";
}
/**
* 跳转到 custom-see.html 页面
*/
@RequestMapping("/custom")
public String customSSEHandle() {
return "/custom-sse";
}
/**
* 定义返回普通响应的处理器方法
* 目的主要是用于对比一下使用 SSE 前后,接口响应的区别。
*/
@RequestMapping("/common")
public void commonHandle(HttpServletResponse response) throws IOException {
doSome(response);
}
/**
* 定义返回默认 SSE 响应的处理器方法
*/
@RequestMapping("/sse/default")
public void defaultHandle(HttpServletResponse response) throws IOException {
// 根据SSE规范进行设置
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
doSome(response);
}
private void doSome(HttpServletResponse response) throws IOException {
PrintWriter out = response.getWriter();
for (int i = 0; i < 10; i++) {
out.println("data:" + i + "\n");
out.println("\r\n");
out.flush();
Timer.sleep(1);
}
}
/**
* 向客户端发送自定义事件的SSE响应
*/
@RequestMapping("/sse/custom")
public void customSSEHandle(HttpServletResponse response) throws IOException {
// 根据SSE规范进行设置
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
PrintWriter out = response.getWriter();
for (int i = 0; i < 10; i++) {
out.println("event:china\n");
out.println("data:" + i + "\n");
out.println("\r\n");
out.flush();
Timer.sleep(1);
}
}
}
- 定义 Thymeleaf 页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>default-sse</title>
</head>
<script type="text/javascript">
// 创建EventSource对象
var es = new EventSource("sse/default");
// 当客户端接收到服务端发送的事件时会触发该方法的执行
es.onmessage = function (ev) {
console.log("my-msg", ev.data, ev);
// 停止接收服务端的事件
if (ev.data == 9) {
es.close();
}
}
</script>
<body>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>custom-sse</title>
</head>
<script type="text/javascript">
var es = new EventSource("sse/custom");
es.addEventListener("china", function (ev) {
console.log("my-msg", ev.data);
if (ev.data == 9) {
es.close();
}
});
</script>
<body>
</body>
</html>
- 测试:http://localhost:8080/default、http://localhost:8080/custom
Reactive Stream
- Reactive Stream:是反应式编程的规范。
- RxJava:是反应式流编程库,出现在 ReactiveStream 规范之前,故其不遵循 Reactive Stream 规范,使用起来也很不方便
- RxJava2:是反应式流编程库,出现在 ReactiveStream 规范之后。尽量遵循 Reactive Stream 规范,又要兼容RxJava,所以有一定的弊端
- Reactor:是全新的反应式流编程库,完全基于ReactiveStream规范的,使用很方便。
Reactive Stream概述
推拉模型与发布/订阅模型
- 在流处理机制中发布/订阅模型可以分为 push(推送)模型和 pull(拉取)模型。push 模型中,发布者将元素主动推送给订阅者。而 pull 模式中,订阅者会向发布者主动索要。
异步系统与背压
- 在同步系统中发布者与订阅者的工作效率相当,发布者发布一个消息后阻塞,等待订阅者消费。订阅者消费完后,订阅者阻塞,等待发布者发布。这种同步式处理方式效率很低。
- 由于同步式处理方式效率很低,一般使用的是异步处理机制。即发布者发布消息,与消费者消费消息的速度是不一样的。那么它们间是如何协调工作的呢?有两种情况:
- 情况一:当订阅者消费速度比发布者发布速度快时,会出现订阅者无消息可消费的情况。
- 情况二:当发布者发布比订阅者消费快时,会出现消息堆积的情况。有两大类解决方案:
- 改变订阅者:设置无边界缓冲区或丢弃无法处理的消息。
- 改变发布者:由订阅者控制发布者发布的速度。这种解决方案称为背压(Back Pressure)。使用背压策略可确保较快的发布者不会压制较慢的订阅者。
反应式流模型
- 反应式流从 2013 年开始,作为提供非阻塞背压的异步流处理标准的倡议,旨在解决处理元素流(即消息流、数据流)的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞,不需要订阅者有无边界缓冲区,不需要订阅者丢弃无法处理的元素。
- 反应式流模型可以解决这个问题,该模型非常简单:订阅者向发布者发送异步请求,订阅 n 个元素;然后发布者向订阅者异步发送 n 个或少于 n 个的元素。反应式流会在 pull 模型和 push 模型流处理机制之间动态切换。当发布者快、订阅者慢时,它使用 pull 模型;当发布者慢、订阅者快时,它使用 push 模型。即谁慢谁占主动。
- 2015 年发布了用于处理反应式流的规范和 Java API。
反应式流规范
- 在 Java 中反应式流规范,是通过 JDK 的 java.util.concurrent.Flow 类中声明的四个内部接口来定义的。这套规范最初是定义在 JDK9 中的。
Flow类
- (这个类用于)创建流控制组件的相关的接口与方法,其中 Publishers 生成由一个或多个 Subscriber 消费的 items,每个 items 由 Subscription 管理。
/**
* Interrelated interfaces and static methods for establishing
* flow-controlled components in which {@link Publisher Publishers}
* produce items consumed by one or more {@link Subscriber
* Subscribers}, each managed by a {@link Subscription
* Subscription}.
*
* @author Doug Lea
* @since 9
*/
public final class Flow {
private Flow() {} // uninstantiable
@FunctionalInterface
public static interface Publisher<T> {
// ...略
}
public static interface Subscriber<T> {
// ...略
}
public static interface Subscription {
// ...略
}
public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> {
}
static final int DEFAULT_BUFFER_SIZE = 256;
public static int defaultBufferSize() {
return DEFAULT_BUFFER_SIZE;
}
}
Publisher<T>接口
- (这是一个)被订阅者接收的 items(与相关控制信息)的生产者。每个当前的订阅者以相同的顺序接受相同的 items(通过 onNext()方法),除非 items 被删除或发生了错误。如果一个发布者发生了“不允许 items 发布给订阅者”的错误,那么订阅者将触发 onError() 方法,并且不再接受消息。否则(发布者没有发生错误),当发布者没有消息再发布给订阅者时,订阅者将触发 onComplete()方法。发布者可以确保每一个订阅的订阅者方法调用都严格按照顺序进行。
- 该段注释不仅说明什么是 Publisher,还说明了其与 Subscriber 的关系,确切地说是 Subscriber 接口中的方法都会在什么时候被触发。
/**
* A producer of items (and related control messages) received by
* Subscribers. Each current {@link Subscriber} receives the same
* items (via method {@code onNext}) in the same order, unless
* drops or errors are encountered. If a Publisher encounters an
* error that does not allow items to be issued to a Subscriber,
* that Subscriber receives {@code onError}, and then receives no
* further messages. Otherwise, when it is known that no further
* messages will be issued to it, a subscriber receives {@code
* onComplete}. Publishers ensure that Subscriber method
* invocations for each subscription are strictly ordered in <a
* href="package-summary.html#MemoryVisibility"><i>happens-before</i></a>
* order.
*/
@FunctionalInterface
public static interface Publisher<T> {
/**
* Adds the given Subscriber if possible. If already
* subscribed, or the attempt to subscribe fails due to policy
* violations or errors, the Subscriber's {@code onError}
* method is invoked with an {@link IllegalStateException}.
* Otherwise, the Subscriber's {@code onSubscribe} method is
* invoked with a new {@link Subscription}. Subscribers may
* enable receiving items by invoking the {@code request}
* method of this Subscription, and may unsubscribe by
* invoking its {@code cancel} method.
*/
public void subscribe(Subscriber<? super T> subscriber);
}
subscribe()
- 这是 publicsher 接口的唯一的方法,该方法用于确立当前发布者与指定的订阅者间的订阅关系。
- 注释翻译:如果可能,添加给定的订阅者。如果已订阅,或者由于策略违反或错误而订阅失败,则使用 Illegalstateexception 调用订阅者的 onError()方法。否则(订阅成功),订阅者的 onSubscribe()方法伴随着新的订阅关系而被调用。订阅者可以通过调用此订阅令牌的 request()方法来接收 item,也可以通过调用其 cancel()方法来取消订阅关系。
Subscriber<T>接口
- (这是一个)消息的接收者。对于每个订阅关系,接口的方法将严格按照串行顺序被调用。
/**
* A receiver of messages. The methods in this interface are
* invoked in strict sequential order for each {@link
* Subscription}.
*/
public static interface Subscriber<T> {
/**
* Method invoked prior to invoking any other Subscriber
* methods for the given Subscription. If this method throws
* an exception, resulting behavior is not guaranteed, but may
* cause the Subscription not to be established or to be cancelled.
*/
public void onSubscribe(Subscription subscription);
/**
* Method invoked with a Subscription's next item. If this
* method throws an exception, resulting behavior is not
* guaranteed, but may cause the Subscription to be cancelled.
*/
public void onNext(T item);
/**
* Method invoked upon an unrecoverable error encountered by a
* Publisher or Subscription, after which no other Subscriber
* methods are invoked by the Subscription. If this method
* itself throws an exception, resulting behavior is
* undefined.
*/
public void onError(Throwable throwable);
/**
* Method invoked when it is known that no additional
* Subscriber method invocations will occur for a Subscription
* that is not already terminated by error, after which no
* other Subscriber methods are invoked by the Subscription.
* If this method throws an exception, resulting behavior is
* undefined.
*/
public void onComplete();
}
onSubscribe()
- 注释翻译:(这是一个)对于给定订阅关系的订阅者的其它方法调用之前先被调用的方法。
- 当 publisher 调用了 subscribe()方法与指定的订阅者确立了订阅关系后,就会触发当前方法的执行。该方法仅会被执行一次。
onNext()
- 注释翻译:(这是一个)调用订阅令牌的下一个 item 的方法。如果该方法抛出异常,结果行为将不能被保证,但可能引起订阅关系的取消。
- 只有当当前的 item 被消费完毕了,才会调用下一个。onNext()方法就是 item 被消费的地方。即当前 onNext()方法的执行完毕,就会触发下一个 onNext()方法的执行。
- onNext()被触发的次数,由订阅者 Subscriber 通过调用 Subscription 的 request()方法请求的消息数量决定。
- 第一次 onNext()方法的执行是由 Subscription 的 request()方法触发。
onError()
- 注释翻译:(这是一个)当发布者或订阅令牌发生不可恢复的错误时调用的方法,在该错误之后,订阅令牌不会再调用其他订阅者方法。如果该方法本身发生异常,结果行为未定义。
- 触发 onError() 方法的事件:
- 发布者发布的消息不是订阅者的需求时;
- 在确定订阅关系时出现异常;
- 当Subscription的request()方法参数小于等于0时。
onComplate()
- 注释翻译:对于不是被错误终止的订阅关系,当订阅者知道没有其它订阅者方法调用再发生时,该方法被调用。对于该订阅关系,该方法之后将不再有其它订阅者方法被调用。如果该方法本身发生异常,结果行为未定义。
- 该方法执行后,onNext()方法将不再执行。
- 当 Publisher 调用了 close()方法后,就表示没有消息再发布了,其会触发 onComplate() 方法的执行。
Subscription接口
- (这是一个)连接发布者与订阅者的消息控制器。只有当请求时订阅者才可接收 items,并且可以在任何时间取消。这个接口中的方法只能被它的订阅者调用。在其它上下文环境中的用法没有定义其效果(即在其它类中调用该接口对象的方法最终的执行效果未定义)。
- 其一般被翻译为“订阅令牌”或“订阅关系”。
- Subscription 的方法只能被订阅者 Subscriber 调用,作为 Subscriber 中调用的对象出现。
/**
* Message control linking a {@link Publisher} and {@link
* Subscriber}. Subscribers receive items only when requested,
* and may cancel at any time. The methods in this interface are
* intended to be invoked only by their Subscribers; usages in
* other contexts have undefined effects.
*/
public static interface Subscription {
/**
* Adds the given number {@code n} of items to the current
* unfulfilled demand for this subscription. If {@code n} is
* less than or equal to zero, the Subscriber will receive an
* {@code onError} signal with an {@link
* IllegalArgumentException} argument. Otherwise, the
* Subscriber will receive up to {@code n} additional {@code
* onNext} invocations (or fewer if terminated).
*/
public void request(long n);
/**
* Causes the Subscriber to (eventually) stop receiving
* messages. Implementation is best-effort -- additional
* messages may be received after invoking this method.
* A cancelled subscription need not ever receive an
* {@code onComplete} or {@code onError} signal.
*/
public void cancel();
}
request()
- 注释翻译:(这个方法用于)给未满足需求的订阅令牌添加指定数量 n 的 items。若 n 小于等于 0,订阅者将触发 onError()方法,并被标记为 IllegalArgumentException 异常。否则(即 n 大于 0),订阅者将触发 n 次 onNext()调用(或更少,如果终止)。
cancel()
- 注释翻译:(这个方法会)导致订阅者最终停止接收消息。实现会尽力而为(尽力不再接收其它消息)——该方法被调用后可能还会收到其它消息。一个被取消的订阅令牌不再需要接收 onComplate()或 onError()的信号。
- 该方法的执行原理上会导致 onNext()方法的停止执行,但实际有可能 onNext()并没有马上停止执行。
三个接口的关系
Processor<T, R>接口
/**
* A component that acts as both a Subscriber and Publisher.
*/
public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> {
}
- Processor,即处理器,充当订阅者和发布者的处理阶段。Processor 接口继承了 Publisher 和 Subscriber 接口,对于发布者来说,Processor 是订阅者,对于订阅者来说,Processor 是发布者。
- Processor 用于转换发布者/订阅者管道中的元素。Processor<T, R>会将来自于发布者的 T 类型的消息数据,接收并转换为 R 类型的数据,并将转换后的 R 类型数据发布给订阅者。一个发布者可以拥有多个处理者。
发布者类SubmissionPublisher
- 通常情况下,我们会使用 JDK 中已经定义好的一个发布者类 SubmissionPublisher,该类就可以完成一个简单的消息生成与发布。从该类的注释第一段中可以了解到其简介。
- (当前的 SubmissionPublisher 类)是一个发布者,它能够以异步方式将已被提交的非空元素发布给订阅者,直到该发布者被关闭。每个当前订阅者都会以相同的顺序接收新提交的元素,除非遇到删除或异常。使用 SubmissionPublisher 对象允许元素生成器以符合响应流的方式工作。发布者依靠丢弃处理和/或阻塞来进行流量控制。
- SubmissionPublisher 是一个发布者,能够以响应流的方式生成消息元素,以异步方式发布消息元素。
/**
* A {@link Flow.Publisher} that asynchronously issues submitted
* (non-null) items to current subscribers until it is closed. Each
* current subscriber receives newly submitted items in the same order
* unless drops or exceptions are encountered. Using a
* SubmissionPublisher allows item generators to act as compliant <a
* href="http://www.reactive-streams.org/"> reactive-streams</a>
* Publishers relying on drop handling and/or blocking for flow
* control.
* @since 9
*/
public class SubmissionPublisher<T> implements Publisher<T>,
AutoCloseable {
// ...略
}
submit()
- 该发布者类中有一个很重要的方法 submit(),用于发布指定消息到订阅令牌。
/**
* Publishes the given item to each current subscriber by
* asynchronously invoking its {@link Flow.Subscriber#onNext(Object)
* onNext} method, blocking uninterruptibly while resources for any
* subscriber are unavailable.
*/
public int submit(T item) {
return doOffer(item, Long.MAX_VALUE, null);
}
- 注释翻译:将给定 item 发布到每个当前的订阅者,并通过异步方式调用其 onNext()方法。当任何为订阅者准备的资源不可用时会阻塞,并且阻塞将不会被打断。
close()
/**
* Unless already closed, issues {@link
* Flow.Subscriber#onComplete() onComplete} signals to current
* subscribers, and disallows subsequent attempts to publish.
* Upon return, this method does <em>NOT</em> guarantee that all
* subscribers have yet completed.
*/
public void close() {
if (!closed) {
BufferedSubscription<T> b;
synchronized (this) {
// no need to re-check closed here
b = clients;
clients = null;
owner = null;
closed = true;
}
while (b != null) {
BufferedSubscription<T> next = b.next;
b.next = null;
b.onComplete();
b = next;
}
}
}
- 注释翻译:除非已经关闭,否则会向当前所有订阅者发出 onComplete()信号,并且不允许后续发布尝试。返回时,该方法不保证所有订阅者都已完成。
代码演示 04-reactive-stream
- 建一个普通的 Maven 工程,命名为 04-reactive-stream,要求 JDK为9及以上。
“发布-订阅”模式反应式流编程
- 定义订阅者:
public class SomeSubscriber implements Flow.Subscriber<Integer> {
/**
* 声明订阅令牌
*/
private Flow.Subscription subscription;
/**
* 当订阅关系确立后,触发该方法的执行(只执行一次)
* 即Publisher的subscribe()方法的执行会触发该方法的执行
*/
@Override
public void onSubscribe(Flow.Subscription subscription) {
System.out.println("--- 执行订阅者的onSubscribe()方法 ---");
this.subscription = subscription;
// 首次订阅8条消息
this.subscription.request(8);
}
/**
* 消息在这里进行消费,该方法的第一次触发是由onSubscribe()方法中的 this.subscription.request(8); 触发
*/
@Override
public void onNext(Integer item) {
System.out.println("订阅者正在处理的消息数据为:" + item);
Timer.sleepMills(50);
// if (item < 30) {
// this.subscription.cancel();
// }
// 订阅者每消费一条消息,就再请求10条消息
this.subscription.request(10);
}
/**
* 当发布者发布过程中,或订阅关系确立过程中,或订阅者请求消息过程中,或消费者消费过程中
* 出现异常,则会触发该方法的执行,该方法的执行会导致 onNext() 方法不再执行
*/
@Override
public void onError(Throwable throwable) {
System.out.println(" --- 执行onError()方法 ---");
throwable.printStackTrace();
this.subscription.cancel();
}
/**
* 当所有消息消费完毕后会执行该方法
* Publisher的close()方法执行完毕后会触发该方法的执行
*/
@Override
public void onComplete() {
System.out.println("发布者已关闭,订阅者将所有消息全部处理完成");
}
}
- 定义测试类:
public class SomeSubscriberTest {
public static void main(String[] args) {
SubmissionPublisher<Integer> publisher = null;
try {
// 创建一个发布者
publisher = new SubmissionPublisher<>();
// 创建一个订阅者
SomeSubscriber subscriber = new SomeSubscriber();
// 确立订阅关系,该方法的执行与其触发的onSubscribe()方法是异步执行的
publisher.subscribe(subscriber);
// 生产消息
for (int i = 0; i < 300; i++) {
// 生产一个[0, 100)随机数
int item = new Random().nextInt(100);
System.out.println("生成第" + i + "条消息" + item);
// 发布消息,当publisher的buffer满时该方法会阻塞
publisher.submit(item);
}
} finally {
publisher.close();
}
System.out.println("主线程开始等待");
Timer.sleep(100);
}
}
“发布-处理-订阅”模式响应流编程
- 前面的例子是,发布者发布的所有 Integer 消息均会被订阅者全部消费。但这里有一个需求:将发布者发布的大于 50 的消息过滤掉,并将小于 50 的 Integer 消息转换为 String 后发布给订阅者。注意:要求发布者必须要发布所有消息,不能让发布者自己过滤掉大于 50 的消息,大于 50 的消息也必须发布。
- 此时就需要借助处理器 Processor 来完成了。处理器将发布者的整数数据,经过处理器过滤处理后,变为了 String 数据。这样的话,订阅者所消费的数据就成为了 String 类型了。
- 定义处理器:
public class SomeProcessor extends SubmissionPublisher<String> implements Flow.Processor<Integer, String> {
/**
* 声明订阅令牌
*/
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
this.subscription.request(8);
}
@Override
public void onNext(Integer item) {
System.out.println("--- 处理器正在处理的消息数据为:" + item);
// 若来自于真正publisher的消息大于等于50,则该消息被丢掉
// 即processor传给真正的subscriber的消息是小于50的消息
if (item < 50) {
this.submit("消息已处理:" + item);
}
Timer.sleepMills(50);
this.subscription.request(10);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
System.out.println("处理器已将消息处理完毕");
this.close();
}
}
- 修改订阅者:
public class SomeSubscriber implements Flow.Subscriber<String> {
/**
* 声明订阅令牌
*/
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
this.subscription.request(8);
}
@Override
public void onNext(String item) {
System.out.println("订阅者正在处理的消息数据为:" + item);
Timer.sleepMills(50);
this.subscription.request(10);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
System.out.println("发布者已关闭,订阅者将所有消息全部处理完成");
}
}
- 定义测试类:
public class SomeTest {
public static void main(String[] args) {
SubmissionPublisher<Integer> publisher = null;
try {
publisher = new SubmissionPublisher<>();
// 创建订阅者
SomeSubscriber subscriber = new SomeSubscriber();
// 创建处理器
SomeProcessor processor = new SomeProcessor();
// 建立订阅关系
publisher.subscribe(processor);
processor.subscribe(subscriber);
for (int i = 1; i <= 300; i++) {
int item = new Random().nextInt(100);
System.out.println("开始生产消息" + i);
publisher.submit(item);
}
} finally {
publisher.close();
}
System.out.println("主线程开始等待");
while (Thread.activeCount() > 2) {
}
}
}