webflux源码解析(2)-reactor

目录

  • [1. 接口概述](#1. 接口概述)
  • [2. 方法执行](#2. 方法执行)
    • [2.1 subscribe方法说明](#2.1 subscribe方法说明)
    • [2.2 processor 方法说明](#2.2 processor 方法说明)
  • [3. 其它示例说明](#3. 其它示例说明)
  • 参考:

webflux框架是基于Project Reactor构建的,是一种响应式的编码方式,因此webflux的源码跟其它框架看起来差别较大。代码实际执行时也是异步的,哪怕通过debug断点跟进流程,也显得有些难懂。

为了看明白webflux的代码,本文梳理了reactor的基本接口设计、代码执行逻辑。

通过逐个方法的跟进,可以发现,所谓的响应式编程并不是魔法,对于具体的方法而言,还是一个线程同步、阻塞的去执行。所谓的响应式,需要相对应的客户端配合,这个待后续文章讨论。

1. 接口概述

分析之前,先看一下reactor提供的顶级接口


发布者
Publisher 是一个可以发送无限序列元素的发布者,允许调用多次,每次调用都会启动一个新的Subscription。每个Subscription只能被一个Subscriber使用;Subscriber消费者只能订阅一次Publisher。

//发布者
public interface Publisher<T> {
    /**
     * 订阅方法
     * 请求发布者启动数据流
     * @param s   消费者
     */
    public void subscribe(Subscriber<? super T> s);
}

订阅者(消费者)

其中Subscriber#onSubscribe只会被调用一次

public interface Subscriber<T> {
    /**
     * 该方法在调用Publisher#subscribe(Subscriber)后执行
     * 在Subscription#request(long)调用之前不会有数据流消费
     * 如果订阅者想要消费更多的数据,需要调用Subscription#request(long)来请求数据。
     */
    public void onSubscribe(Subscription s);
    /**
     * 消费下一个消息
     * 在调用Subscription#request(long)方法时,Publisher会通过这个方法来通知订阅者消息
     * @param t 数据元素
     */
    public void onNext(T t);
    public void onError(Throwable t);
    public void onComplete();
}

Subscription

public interface Subscription {
    public void request(long n);
    public void cancel();
}
  • Subscription的生命周期是订阅者对发布者的一次消费
  • 一个Subscription只能被一个Subscriber使用
  • Subscription不仅允许请求数据,也允许取消对数据的请求,并且支持资源清理
  • 在通过org.reactivestreams.Subscription#request发送需求信号之前,不会有任何事件产生。该方法请求的数量最大是 Long.MAX_VALUE

Processor

一种即是发布者又是消费者的组件

public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

2. 方法执行

2.1 subscribe方法说明

示例:

Flux.just(1, 2, 3, 4, 5)
	.subscribe(new CoreSubscriber<>() {//这里传入CoreSubscriber对象作为订阅者
		@Override
		public void onSubscribe(Subscription s) {
			log.info("onSubscribe, {}", s.getClass());
			s.request(5);
		}
		@Override
		public void onNext(Integer integer) {
			log.info("onNext: {}", integer);
		}
		@Override
		public void onError(Throwable t) {
		}
		@Override
		public void onComplete() {
			log.info("onComplete");
		}
	});

以下是调用的方法跟踪,调用 Flux.just 方法:


查看示例中的代码可知 subscribe() 方法调用的是 FluxArray#subscribe

通过 FluxArray#subscribe 方法调用到我们定义的 onSubscribe 方法,进而调用 FluxArray.ArraySubscription#request 方法

FluxArray.ArraySubscription#slowPath 方法中循环拉去数组中的元素,执行onNext方法,最后执行 onComplete 方法

[!NOTE] 总结

  • Publisher中定义了数据源
  • Subscriber 中定义了数据处理的动作
  • Subscription 中定义了数据处理的过程,编排了 Subscriber 中定义的方法

2.2 processor 方法说明

示例:

Flux.just(1, 2, 3, 4, 5)
	.map(i -> i * i)
	.subscribe(new CoreSubscriber<>() {
		//省略,与之前相同
	});

跟进执行的方法到 Flux#map 方法:

之后调用的 subscribe 方法是 FluxMapFuseable#subscribe

此时执行 FluxArray#subscribe 传入的不再是 FluxArray.ArraySubscription 对象,变成了 FluxMapFuseable.MapFuseableSubscriber 对象了,由于中间过程的组装,之后会进一步调用至 FluxMapFuseable.MapFuseableSubscriber#onSubscrib

此处调用 onSubscribe 方法时,传入的参数为this,即位为 MapFuseableSubscriber 对象,因此执行s.request(5) 时,s 是 MapFuseableSubscriber 对象,实际执行的方法是
FluxMapFuseable.MapFuseableSubscriber#request

最终还是委托 FluxArray.ArraySubscription#request 方法执行。由于这一层封装,导致执行 onNext 方法时,执行的是 FluxMapFuseable.MapFuseableSubscriber#onNext 方法,这里充当了消费者的角色

真的很绕

3. 其它示例说明

这是一个更通用的场景,通过异步任务定义Publisher

测试示例:

public void test(){  
    Mono.fromCallable(() -> {  
                // 执行阻塞操作,例如数据库查询  
                return "Some blocking operation result";  
            })  
            .subscribe(result -> {  
                System.out.println("Result: " + result);  
            });  
}

调用方法 Mono#fromCallable 时,返回的是 MonoCallable 对象

调用 subscribe() 方法时,调用的是 Mono#subscribe 方法,但会逐层封装(此处封装为LambdaMonoSubscriber

最终在此会调用到不同的实现,此处 this 即为 MonoCallable 对象

调用的subscribe最终为:MonoCallable#subscribe

actual 是 LambdaMonoSubscriber 对象,调用其 subscribe 方法

查看 LambdaMonoSubscriber#onSubscribe

继续跟进,调用MonoSubscriber#request

因此,此处actual.onSubscribe(sds)并没有实际意义

LambdaMonoSubscriber.complete 方法执行时,调用的是 LambdaMonoSubscriber 的onNext、onComplete方法,实际执行的就是示例中定义的
System.out.println("Result: " + result)

至此,全部方法执行完毕。

参考:

[1] reactor3 源码分析

相关推荐
狂放不羁霸8 分钟前
idea | 搭建 SpringBoot 项目之配置 Maven
spring boot·maven·intellij-idea
九圣残炎9 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge11 分钟前
Netty篇(入门编程)
java·linux·服务器
计算机学长felix36 分钟前
基于SpringBoot的“校园交友网站”的设计与实现(源码+数据库+文档+PPT)
数据库·spring boot·毕业设计·交友
Re.不晚39 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐44 分钟前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
江深竹静,一苇以航1 小时前
springboot3项目整合Mybatis-plus启动项目报错:Invalid bean definition with name ‘xxxMapper‘
java·spring boot
confiself1 小时前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言