JavaEE 企业级分布式高级架构师(十四)ReactiveStream编程WebFlux(2)

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) {
        }
    }
}
开课吧-javaEE企业级分布式高级架构师是一门专注于培养企业级应用开发的高级技术课程。该课程旨在帮助学员全面掌握Java EE企业级开发的技能和知识,培养他们成为具备分布式应用系统设计和架构能力的高级架构师。 在这门课程中,学员将学习Java EE的核心概念和技术,包括Servlet、JSP、JDBC、EJB、JNDI等。同时,学员还将深入学习分布式应用开发的相关技术,如Web服务、消息队列、分布式缓存、负载均衡等。除此之外,课程还将涉及如何使用行的Java EE开发框架(如Spring、Hibernate等)进行企业应用开发,并介绍分布式系统的设计原则和最佳实践。 通过学习这门课程,学员将能够了解分布式应用架构的基本原理,并具备设计和构建分布式应用系统的能力。他们将熟练掌握Java EE平台的各种技术和工具,能够灵活运用它们开发高性能、可扩展性强的企业级应用系统。此外,通过课程中的实战项目,学员还将锻炼解决实际问题和项目管理的能力。 作为一门高级架构师的课程,它将帮助学员进一步提升自己的职业发展。毕业后,学员可以在企业中担任分布式应用的架构师、系统设计师、技术经理等角色,负责企业级应用系统的设计和开发。此外,他们还可以选择独立开发,提供技术咨询和解决方案。 总之,开课吧-javaEE企业级分布式高级架构师是一门非常有价值的课程,它将帮助学员掌握Java EE企业级开发的核心技术和分布式应用架构的设计原理,培养他们成为具备高级架构师能力的软件开发专业人士。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值