Reactor-core 响应式编程 spring-boot-starter-webflux

响应式编程概念:

(1)响应式编程=数据流+变化传递+声明式

响应式编程 是一种不同于Servlet的全新的编程范式和技术栈,它基于异步非阻塞的特性 ,能够借助EventLoop以少量线程应对高并发的访问,对微服务架构也颇有助益。

异步非阻塞并不会使程序运行得更快。WebFlux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。Spring WebFlux 是一个异步非阻塞的 Web 框架,所以,它特别适合应用在 IO 密集型的服务中。

(2)在Java 9版本中,响应式流的规范被纳入到了JDK中,相应的API接口是java.util.concurrent.Flow。

(3)Spring推出的响应式模块Spring-WebFlux,首选Reactor作为其响应式技术栈的一部分。

(4)在响应式流中,数据流的发出者(发布者)叫做Publisher ,监听者(订阅者)叫做Subscriber。订阅者处理完一个元素的时候通过request(1)跟发布者再请求一个元素

(5)流量控制------回压

1)假如发布者发出数据的速度和订阅者处理数据的速度不同的时候,如果没有流量控制,那么订阅者会被发布者快速产生的数据流淹没。

2)订阅者需要有一种能够向上游反馈流量需求的机制:回压(背压)

3)回压的处理会涉及不同的策略:

3-1)缓存的策略:处理方式与消息队列有些相似之处,发布者需要维护一个队列用来缓存还没有被处理的元素。通常用于对数据准确性要求比较高的场景,比如发布者这儿是突然到来的数据高峰,都是要保存到数据库的,作为订阅者没有那么快的处理速度,那么发布者就需要将数据暂时缓存起来。

3-2)丢弃的策略:发布者不需要缓存来不及处理的数据,而是直接丢弃,当订阅者请求数据的时候,会拿到发布者那里最近的一个数据元素。

在Reactor 3中如何通过generate和create等方法生成自定义的数据流,是命令式编程与响应式编程的桥梁。

Reactor API

Reactor 提供了实现 Publisher 的响应式类 Flux 和 Mono,以及丰富的操作符。一个Flux 代表 0...N 个元素的响应式流 ;一个Mono 代表 0|1 个元素的响应式流 (Flux的方法它大多也都能用)

Flux 和 Mono 之间可以转换,比如 Flux 的 count 操作(计算流中元素个数)返回 Mono,Mono 的 concatWith 操作(连接另一个响应式流)返回 Flux。

一、Flux & Mono(Publisher对象)

1****.**** ****Flux<T> 是一个能够发出 0 到 N 个元素的标准 Publisher<****T>,它会被一个完成(completion)或错误(error)信号终止。因此,一个 Flux 的可能结果是 value、completion

或 error,这三个分别会传递给订阅者中的 onNext、onComplete、onError 方法。

注意:所有的信号事件,包括代表终止的信号事件都是可选的。 如果没有 onNext 事件,但是有 onComplete 事件,那么发出的就是空的有限流;如果去掉 onComplete

就得到一个 无限的空数据流。无限的数据流可以不是空的,比如 Flux.interval(Duration) 生成的是一个 Flux,这是一个无限周期性发出规律整数的时钟数据流。

2.Mono<T>一种特殊的Publisher<T>(Flux的方法它大多也都能用) ,它最多只能发出一个元素,然后(可选的)终止于 onComplete 或 onError 信号。

Mono 中的操作符是 Flux 中操作符的子集,即 Flux 中只有部分操作符适用于 Mono,有些操作符是将 Mono 和另一个 Publisher 连接转换为 Flux。例如,Mono#concatWith(Publisher) 转换为 Flux,Mono#then(Mono) 返回另一个 Mono。

注意:可以使用 Mono 来创建一个只有完成概念的空值异步处理过程(类似于 Runnable)。

二、构建Flux&Mono 基本api:

1.just (Flux/Mono) : 使用提供的元素发出数据然后结束的流。

Flux<Integer> flux = Flux.just(1, 2, 3, 4, 5); flux.subscribe( value -> System.out.println("Received: " + value), error -> System.err.println("Error: " + error), () -> System.out.println("Completed") );

Mono<Integer> mono = Mono.just(1); mono.subscribe( value -> System.out.println("Received: " + value), error -> System.err.println("Error: " + error), () -> System.out.println("Completed") );

just 接收的是一个可变长参数,其实生成的就是 5 个整数的元素序列就是publisher

subscribe方法是订阅的 意思也就是消费元素 的下游方法。就是Subscriber

2.Flux#fromXxx

Flux 提供了 fromArray(从数组)、fromIterable(从迭代器)、fromStream(从 Java Stream 流) 的方式来创建 Flux。

String[] array = new String[]{"hello", "reactor", "flux"};

List<String> iterable = Arrays.asList("foo", "bar", "foobar"); Flux.fromArray(array).subscribe(System.out::println); Flux.fromIterable(iterable).subscribe(System.out::println); Flux.fromStream(Arrays.stream(array)).subscribe(System.out::println);

3.Flux#range

从 start 开始构建一个 Flux,该 Flux 仅发出一系列递增计数的整数。 也就是说,在 start(包括)和 start + count(排除)之间发出整数,然后完成

Flux.range(3, 5).subscribe(System.out::println);

4.Flux#interval

interval 生成的是一个无限数据流。

在全局计时器上创建一个 Flux,该 Flux 在初始延迟后,发出从0开始并以指定的时间间隔递增的长整数。 如果未及时产生,则会通过溢出 IllegalStateException 发出 onError信号,详细说明无法发出的原因。 在正常情况下,Flux 将永远不会完成。interval 提供了 3 个重载方法,三者的区别主要在于是否延迟发出、以及使用的调度器。

Flux<Long> interval(Duration period)

没有延迟,按照 period 的周期立即发出,默认使用 Schedulers.parallel() 调度器

Flux<Long> interval(Duration delay, Duration period)

以 delay 延迟,按照 period 的周期发出,默认使用 Schedulers.parallel() 调度器

Flux<Long> interval(Duration delay, Duration period, Scheduler timer)

以 delay 延迟,按照 period 的周期发出,使用指定的调度器

Flux.interval(Duration.ofMillis(30), Duration.ofMillis(500)).subscribe(System.out::println);

5.Flux# create

Flux.create(emitter->{

emitter.next(1); emitter.next(2); emitter.complete();

}).subscribe(System.out::println);

注意:Flux这里的next 可以调用多次( Flux发出 0 到 N 个元素)。

Mono.create(monoSink -> {

monoSink. success(1);

}).subscribe(System.out::println);

注意:Mono这里创建元素且只能创建一个。且没有complete等方法(Mono 0或者1个元素)。

6.Flux# error

创建一个立即抛出指定错误的 Mono

Mono.error(Throwable error)

三、Reactor 操作符(常用)

在 Reactor 中,每个操作符对 Publisher 进行处理,然后将 Publisher 包装为另一个新的 Publisher 。就像一个链条,数据源自第一个 Publisher ,然后顺链条而下,在每个环节进行相应的处理。最终,订阅者(Subscriber )终结这个过程。所以, 响应式编程按照链式方式进行开发。

如同 Java Stream 的中间操作一样,Reactor 的 Flux 和 Mono 也为我们提供了多种操作符

转换类的操作符数量最多,平常过程中也是使用最频繁的。

0.timeout 操作符: 可以在流中发生超时时发出一个默认值

Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(1))

.timeout(Duration.ofMillis(500), Mono.just(0)).subscribe(System.out::println);

1.as: 将响应式流转换为目标类型,既可以是非响应式对象,也可以是 Flux 或 Mono。

Flux.range(3, 8).as(Mono::from) .subscribe(System.out::println);

2.cast: 将响应式流内的元素强转为目标类型,如果类型不匹配(非父类类型或当前类型),将抛出 ClassCastException。

Flux.range(1, 3) .cast(Number.class) .subscribe(System.out::println);

3-1.collect

通过应用收集器,将 Flux 发出的所有元素收集到一个容器中。当此流完成时,发出收集的结果

Flux.range(1, 5) .collect(Collectors.toList()) .subscribe(System.out::println);

3-2.collectList收集到一个列表中,当此流完成时,发出收集的结果

Flux.range(1, 5) .collectList() .subscribe(System.out::println);

4.collectMap 收集到map中,当此流完成时,发出收集的结果

Flux.just(1, 2, 3, 4, 5, 3, 1) .collectMap(n -> n, n -> n + 100) .subscribe(System.out::println);

spring-boot-starter-webflux

1.pom.xml引入依赖:

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-webflux</artifactId>

</dependency>

2.Spring WebFlux支持基于注释的响应式编程写法,与Spring MVC非常相近:

复制代码
 @RestController
 @RequestMapping("/users")
 public class MyRestController {
     @GetMapping("/{user}")
     public Mono<User> getUser(@PathVariable Long user) {
     // ...
     }
     @GetMapping("/{user}/customers")
     public Flux<Customer> getUserCustomers(@PathVariable Long user) {
     // ...
     }
     @DeleteMapping("/{user}")
     public Mono<User> deleteUser(@PathVariable Long user) {
     // ...
     }
 } 

3.实战demo案例:

复制代码
@PostMapping("/ctrl/{modelApiCode}")
public Mono<Result> ctrl(@PathVariable String modelApiCode, @RequestBody ProtocolModelApiInvokeParam param) {
    Object[] objects = this.getProtocolModelApi(modelApiCode, param);
    DeviceProtocolSupplier supplier = (DeviceProtocolSupplier) objects[3];
    //返回一个Mono对象
    return Mono.<InvokeResult>create(item -> { //Mono.create创建一个Publisher发布者
        try {
            RealtimeStatus realtimeStatus = cacheManager.get(supplier.getProtocol().getCode(), param.buildKey());
            if(realtimeStatus.getStatus() == DeviceStatus.offline) {
                if(realtimeStatus.getType() == DeviceType.Direct) {
                    //item.success固定语法: Publisher发出元素
                    item.success(InvokeResult.fail("设备不在线["+realtimeStatus.getDeviceName()+"]", null)); return;
                }
            }

            ProtocolInvokeUtil.invoke(supplier, (ModelApi) objects[1], param, realtimeStatus, result -> {
                //item.success固定语法: Publisher发出元素
                item.success(result);
                // 将实时结果写出到前端
                IotThreadManager.instance().getExecutorService().execute(() -> {
                    realtimeListener.push(param.getUid(), (ModelApi) objects[1], result);
                });
            });
        } catch (ProtocolInvokeException e) {
            e.printStackTrace();
            //item.success固定语法: Publisher发出元素
            item.success(InvokeResult.fail(e.getMessage(), null));
        }
    }).timeout(Duration.ofSeconds(10), Mono.error(new ServiceException("执行超时")))//timeout超时 则抛出异常
    .map(item -> {//Reactor 操作符map,对Mono.create中item.success()发出的元素进行map转换处理
        if(item.getStatus() == ExecStatus.success) {
            return Result.success(item.getValue());//返回Result对象,展示到前端
        } else {
            return Result.fail(item.getReason());//返回Result对象,展示到前端
        }
    });
}
相关推荐
WiChP18 小时前
【V0.1B5】从零开始的2D游戏引擎开发之路
java·服务器·数据库
cch891818 小时前
汇编与Java:底层与高层的编程对决
java·开发语言·汇编
荒川之神19 小时前
拉链表概念与基本设计
java·开发语言·数据库
cch891819 小时前
汇编与Go:底层到高层的编程差异
java·汇编·golang
chushiyunen19 小时前
python中的@Property和@Setter
java·开发语言·python
禾小西20 小时前
Java中使用正则表达式核心解析
java·python·正则表达式
2401_8955213420 小时前
SpringBoot Maven快速上手
spring boot·后端·maven
yoyo_zzm20 小时前
JAVA (Springboot) i18n国际化语言配置
java·spring boot·python
APIshop20 小时前
Java获取京东商品详情接口(item_get)实战指南
java·linux·数据库