Spring webflux基础核心技术

一、 用操作符转换响应式流

1 、 映射响应式流元素

转换序列的最自然方式是将每个元素映射到一个新值。
Flux 和 Mono 给出了 map 操作符,具有 map(Function<T,R>) 签名的方法可用于逐个处理元素。
当操作符将元素的类型从 T 转变为 R 时,整个序列的类型将改变。

Flux.map() 的弹珠图

image.png

index 操作符可用于枚举序列中的元素。该方法具有以下签名: Flux<Tuple2<Long,T >> index() 。

image.png

timestamp 操作符的行为与 index 操作符类似,但会添加当前时间戳而不是索引。

image.png

2、 过滤响应式流

Project Reactor 包含用于过滤元素的各种操作符。

  1. filter 操作符仅传递满足条件的元素。
  2. take(n) 操作符限制所获取的元素,该方法忽略除前 n 个元素之外的所有元素。
  3. takeLast 仅返回流的最后一个元素。
  4. takeUntil(Predicate) 传递一个元素直到满足某个条件。
  5. elementAt(n) 只可用于获取序列的第 n 个元素。
  6. takeUntilOther(Publisher) 或 skipUntilOther(Publisher) 操作符,可以跳过或获取一个元素,直到某些消息从另一个流到达。

考虑如下工作流程,该工作流中,首先开始一个流的处理,然后从其他流收到特定事件之后,停止该流的处理。

image.png

3、 收集响应式流

  • 收集到List
  • 使用 collectMap 操作符的映射( Map<K,T> );
  • 使用 collectMultimap 操作符的多映射( Map<K,Collection<T>> );
  • Flux.collect(Collector) 操作符收集到任何实现了 java.util.stream.Collector 的数据结构。
  • Flux 和 Mono 都有 repeat() 方法和 repeat(times) 方法,这两种方法可以针对传入序列进=行循环操作。
  • defaultIfEmpty(T) 是另一个简洁的方法,它能为空的 Flux 或 Mono 提供默认值。
  • Flux.distinct() 仅传递之前未在流中遇到过的元素。
  • Flux.distinctUntilChanged() 操作符没有此限制,可用于无限流以删除出现在不间断行中的重复项。
3.1 收集到List
Flux.just(1,2,36,4,25,6,7)//CollectionSoredList 默认是升序.collectSortedList(Comparator.reverseOrder()).subscribe(System.out::println);

请注意,收集集合中的序列元素可能耗费资源,当序列具有许多元素时这种现象尤为突出。此外,尝试在无限流上收集数据可能消耗所有可用的内存。

3.2 使用collectMap
Flux.just(1,2,3,4,5,6).collectMap(item -> "key:num" + item).subscribe(System.out::println);
Flux.just(1,2,3,4,5,6).collectMap(item -> "key:" + item,item -> "value:" + item).subscribe(System.out::println);Flux.just(1,2,3,4,5,6).collectMap(integer -> "key:" + integer,integer -> "value:" + integer,()->{Map<String,String> map = new HashMap<>();for(int i =7 ;i <10;i++){map.put("key:" + i ,"value:" + i);}return map;}).subscribe(System.out::println);
3.3 CollectionMultimap 使用
Flux.just(1,2,3,4,5).collectMultimap(item -> "key:" + item,item ->{List<String> values = new ArrayList<>();for(int i =0 ;i < item ;i ++){values.add("value:" + i);}return values;}).subscribe(System.out::println);
Flux.just(1,2,3,4,5).collectMultimap(item -> "key:" + item,item ->{List<String> values = new ArrayList<>();for(int i =0 ;i < item ;i ++){values.add("value:" + i);}return values;},// 扩充() ->{Map map = new HashMap<String,List>();List<String> list = new ArrayList<>();for(int i = 0 ;i <3;i ++){list.clear();for(int j = 0 ;j <i;j++){list.add("ele:" +j);}map.put(i + ":key",list);}return map;}
).subscribe(System.out::println);
3.4 repeat 操作符的使用:
Flux.just(1,2,3).repeat(3)// 实际上是打印四次,一次原始的,三次重复的.subscribe(System.out::println);
3.5 defaultIfEmpty 操作符的使用:
Flux.empty().defaultIfEmpty("hello msb").subscribe(System.out::println);
3.6 distinct 操作符的使用:
Flux.just(1,2,3).repeat(3).distinct().subscribe(System.out::println);
3.7 distinctUntilChanged 操作符的使用:
 Flux.just(1,1,1,2,2,2,3,3,3,1,1,1,2,2,2).distinctUntilChanged().subscribe(System.out::print);
System.out.println("===============");
Flux.just(1,1,1,2,2,2,3,3,3,1,1,1,2,2,2).distinct().subscribe(System.out::print);

image.png

4、裁剪流中元素

Project Reactor可以:

  1. 统计流中元素的数量;
  2. 检查所有元素是否具有 Flux.all(Predicate) 所需的属性;
  3. 使用 Flux.any(Predicate) 操作符检查是否至少有一个元素具有所需属性;
  4. 使用 hasElements 操作符检查流中是否包含多个元素;
  5. 使用 hasElement 操作符检查流中是否包含某个所需的元素。短路逻辑,在元素与值匹配时立即返回true。
  6. any 操作符不仅可以检查元素的相等性,还可以通过提供自定义 Predicate 实例来检查任何其他属性。

检查序列中是否包含偶数:

Flux.just(1,2,3,4,5,6).any(item -> item % 2 == 0).subscribe(System.out::println);

我们可以检查一下他的整个运行过程: 增加副作用

Flux.just(1,2,3,4,5,6).doOnNext(item -> System.out.println(item)).any(item -> item % 2 == 0).subscribe(System.out::println);
Flux 类能使用自定义逻辑来裁剪序列(也称为折叠)。 reduce 操作符通常需要一个初始值和一个函数,而该函数会将前一步的结果与当前步的元素组合在一起。

将 1 到 5 之间的整数加起来:

Flux.range(1,5).reduce(0,(item1,item2)->{System.out.println("item1:" + item1);System.out.println("item2:" + item2);return item1 + item2;}).subscribe(System.out::println);

Flux.scan()操作符在进行聚合时,可以向下游发送中间结果。

scan 操作符对 1 到 5 之间的整数求和:

Flux.range(1,5).scan(0,(num1,num2) ->{System.out.println("num1:" + num1);System.out.println("num2:" + num2);return num1 + num2;}).subscribe(System.out::println);

运行结果如下:他是将中间结果输出出来

0
num1:0
num2:1
1
num1:1
num2:2
3
num1:3
num2:3
6
num1:6
num2:4
10
num1:10
num2:5
15

scan 操作符对于许多需要获取处理中事件的相关信息的应用程序有用

例如,我们可以计算流上的移动平均值:

int arrLength = 5;
Flux.just(1,2,3,4,5,6).index().scan(new int[arrLength],(arr,entry) ->{arr[(int) (entry.getT1() % arrLength)] = entry.getT2();return arr;
}).skip(arrLength)// 当窗口数组背灌满之后,开始计算平均值,因此要跳过没有灌满的情况.map(array -> Arrays.stream(array).sum() * 1.0 /arrLength).subscribe(System.out::println);

Mono 和 Flux 流有 then、thenMany 和 thenEmpty 操作符,它们在上游流完成时完成。

上游流完成处理后,这些操作符可用于触发新流,订阅是对于新流的。

Flux.just(1,2,3,4).doOnNext(item -> System.out.println("副作用:" + item)).thenMany(Flux.just(5,6,7)).subscribe(System.out::println);

即使 1、2 和 3 是由流生成和处理的,subscribe 方法中的 lambda 也只接收 4 和 5。

5、 组合响应式流

Project Reactor 可以将许多传入流组合成一个传出流。

指定的操作符虽然有许多重载方法,但是都会执行以下转换。

  1. concat 操作符通过向下游转发接收的元素来连接所有数据源。当操作符连接两个流时,它首先消费并重新发送第一个流的所有元素,然后对第二个流执行相同的操作。
  2. merge 操作符将来自上游序列的数据合并到一个下游序列中。与 concat 操作符不同,上游数据源是立即(同时)被订阅的。
  3. zip 操作符订阅所有上游,等待所有数据源发出一个元素,然后将接收到的元素组合到一个输出元素中。
  4. combineLatest 操作符与 zip 操作符的工作方式类似。但是,只要至少一个上游数据源发出一个值,它就会生成一个新值。

concat 操作符的使用:

// 流是按顺序定于,按顺序处理
Flux.concat(Flux.range(10,5).delayElements(Duration.ofMillis(100)).doOnSubscribe(subscription -> System.out.println("订阅第一个流")),Flux.range(100,5).delayElements(Duration.ofMillis(100)).doOnSubscribe(subscription -> System.out.println("订阅第二流"))
).subscribe(System.out::println);
Thread.sleep(10*1000);

merge 操作符的使用:

Flux.merge(Flux.range(10,5).delayElements(Duration.ofMillis(100)).doOnSubscribe(subscription -> System.out.println("订阅第一个流")),Flux.range(100,5).delayElements(Duration.ofMillis(100)).doOnSubscribe(subscription -> System.out.println("订阅第二流"))
).subscribe(System.out::println);
Thread.sleep(10*1000);

zip 操作符的使用:

// zip
//这里最后形成的是一个二元组
// 这里面依照最慢的那个进行打印,如果我们两个流的打印个数不同,那就以最少的为准
Flux.zip(Flux.range(1,10).delayElements(Duration.ofMillis(10)),Flux.range(100,10).delayElements(Duration.ofMillis(10))).subscribe(System.out::println);Thread.sleep(10*1000);

combineLatest 操作符的使用:

Flux.combineLatest(Flux.range(1,10).delayElements(Duration.ofMillis(1000)),Flux.range(100,10).delayElements(Duration.ofMillis(2000)),((integer1, integer2) -> integer1 + "==" +integer2)
).subscribe(System.out::println);
Thread.sleep(10*1000);

image.png

6、流元素批处理

Project Reactor 支持以以下几种方式对流元素( Flux<T> )执行批处理。

  1. 将元素缓冲(buffering)到容器(如 List)中,结果流的类型为 Flux<List<T>> 。
  2. 通过开窗(windowing)方式将元素加入诸如 Flux<Flux<T>> 等流中。请注意,现在的流信号不是值,而是可以处理的子流。
  3. 通过某些键将元素分组(grouping)到具有 Flux<GroupedFlux<K, T>> 类型的流中。每个新键都会触发一个新的 GroupedFlux 实例,并且具有该键的所有元素都将被推送到GroupFlux 类的该实例中。

可以基于以下场景进行缓冲和开窗操作:

  1. 处理元素的数量,比方说每 10 个元素;
  2. 一段时间,比方说每 5 分钟一次;
  3. 基于一些谓语,比方说在每个新的偶数之前切割;
  4. 基于来自其他 Flux 的一个事件,该事件控制着执行过程。

如,为列表(大小为 5)中的整数元素执行缓冲操作:

Flux.range(1,100).buffer(10).subscribe(System.out::println);

buffer 操作符将许多事件收集到一个事件集合中。该集合本身成为下游操作符的事件。当需要使用元素集合来生成一些请求,而不是使用仅包含一个元素的集合来生成许多小请求时,用缓冲操作符来实现批处理会比较方便。如,可以将数据项缓冲几秒钟然后批量插入,而不是逐个将元素插入数据库。

window 操作符:
如果需要根据数字序列中的元素是否为素数进行开窗拆分,可以使用 window 操作符的变体windowUntil。它使用谓词来确定何时创建新切片。

Flux.range(101,20).windowUntil(ReactorDemo12::isPrime,false).subscribe(window ->window.collectList().subscribe(item -> System.out.println("window:" + item)));}private static boolean isPrime(Integer integer){double sqrt = Math.sqrt(integer);if(integer <  2){return false;}if(integer == 2 || integer == 3){return true;}if(integer % 2 == 0){return false;}for(int i = 3;i <= sqrt;i++){if(integer % i == 0){return false;}}return true;
}

请注意第一个窗口为空。这是因为一旦启动原始流,就会生成一个初始窗口。然后,第一个元素会到达(数字 101),它是素数,会触发一个新窗口。因此,已经打开的窗口会在没有任何元素的情况下通过 onComplete 信号关闭。

window操作符和buffer操作符类似,后者仅在缓冲区关闭时才会发出集合,而 window 操作符会在事件到达时立即对其进行传播,以更快地做出响应并实现更复杂的工作流程。
groupBy 操作符通过某些条件对响应式流中的元素进行分组。通过对每个元素打一个标签(key),按照标签将元素进行分组。
如:将整数序列按照奇数和偶数进行分组,并仅跟踪每组中的最后两个元素。

image.png

此外,Project Reactor 库支持一些高级技术,例如在不同的时间窗口上对发出的元素进行分组。

7、flatMapconcatMapflatMapSequential 操作符

flatMap 操作符在逻辑上由 map 和 flatten(就 Reactor 而言,flatten 类似于 merge 操作符)这两个操作组成。
flatMap 操作符的 map 部分将传入的

每个元素转换为响应式流(T -> Flux<R>);

flatten 部分将所有生成的响应式流
合并为一个新的响应式流,通过该流可以传递 R 类型的元素。
Project Reactor 提供了 flatMap 操作符的一些不同变体。除了重载,该库还提供了flatMapSequential 操作符和 concatMap 操作符。

这 3 个操作符在以下几个方面有所不同。

  1. 操作符是否立即订阅其内部流;flatMap 操作符和 flatMapSequential 操作符会立即订阅,而 concatMap 操作符则会在生成下一个子流并订阅它之前等待每个内部完成。

  2. 操作符是否保留生成元素的顺序;concatMap 天生保留与源元素相同的顺序,flatMapSequential 操作符通过对所接收的元素进行排序来保留顺序,而 flatMap 操作符不一定保留原始排序。

  3. 操作符是否允许对来自不同子流的元素进行交错;

    flatMap 操作符允许交错,而 concatMap和 flatMapSequential 不允许交错。

flatMap 操作符(及其变体)在函数式编程和响应式编程中都非常重要,因为它能使用一行代码实现复杂的工作流。

flatMap允许元素交错

flatMap 操作符

Random random = new Random();
Flux.just(Arrays.asList(1,2,3),Arrays.asList("a","b","c","d"),Arrays.asList(7,8,9)).doOnNext(System.out::println).flatMap(item -> Flux.fromIterable(item).doOnSubscribe(subscription -> {System.out.println("已经订阅");})// 我们增加一个延时,订阅后延时一段时间再发送.delayElements(Duration.ofMillis(random.nextInt(100) + 100))).subscribe(System.out::println);Thread.sleep(10*1000);

flatMap 弹珠图:

image.png

concatMap不允许元素交错。

concatMap 操作符的使用:

Random random = new Random();
Flux.just(Arrays.asList(1,2,3),Arrays.asList("a","b","c","d"),Arrays.asList(7,8,9)).doOnNext(System.out::println).concatMap(item -> Flux.fromIterable(item).doOnSubscribe(subscription -> {System.out.println("已经订阅");})// 我们增加一个延时,订阅后延时一段时间再发送.delayElements(Duration.ofMillis(random.nextInt(100) + 100))).subscribe(System.out::println);Thread.sleep(10*1000);

concatMap对每个上游的元素,在接收后都立即生成新的流,新流每个元素处理完之后,进行下一个新流的处理。

image.png

flatMapSequential不允许元素交错。

flatMapSequential 操作符的使用:

Random random = new Random();
Flux.just(Arrays.asList(1,2,3),Arrays.asList("a","b","c","d"),Arrays.asList(7,8,9)).doOnNext(System.out::println).flatMapSequential(item -> Flux.fromIterable(item).doOnSubscribe(subscription -> {System.out.println("已经订阅");}).delayElements(Duration.ofMillis(random.nextInt(100) + 100))).subscribe(System.out::println);
Thread.sleep(10*1000);

image.png

8、元素采样

对于高吞吐量场景而言,通过应用采样技术处理一小部分事件是有意义的。

sample 操作符和 sampleTimeout 操作符可以让流周期性地发出与时间窗口内最近看到的值相对应的数据项。

我们假设使用以下代码:

// 每个100ms从流中获取对应的元素
Flux.range(1,100).delayElements(Duration.ofMillis(10)).sample(Duration.ofMillis(200)).subscribe(System.out::println);
Thread.sleep(10*1000);

使我们每10毫秒都顺序生成数据项,订阅者也只会收到所指定的约束条件内的一小部分事件。通过这种方法,我们可以在不需要所有传入事件就能成功操作的场景下使用被动限速。流控。
sample弹珠图:

image.png

sampleTimeout 操作符:

Random random = new Random();
Flux.range(0,20).delayElements(Duration.ofMillis(100)).sampleTimeout( item -> Mono.delay(Duration.ofMillis(random.nextInt(100) + 50)),20).subscribe(System.out::println);
Thread.sleep(10 * 1000);

流控。

image.png

9、 将响应式流转化为阻塞结构

Project Reactor 库提供了一个 API,用于将响应式流转换为阻塞结构。

有以下选项来阻塞流并同步生成结果:

  1. toIterable 方法将响应式 Flux 转换为阻塞 Iterable。
  2. toStream 方法将响应式 Flux 转换为阻塞 Stream API。从 Reactor 3.2 开始,在底层使用toIterable 方法。
  3. blockFirst 方法阻塞了当前线程,直到上游发出第一个值或完成流为止。
  4. blockLast 方法阻塞当前线程,直到上游发出最后一个值或完成流为止。在 onError的情况下,它会在被阻塞的线程中抛出异常。
blockFirst 操作符和 blockLast 操作符具有方法重载,可用于设置线程阻塞的持续时间。这应该可以防止线程被无限阻塞。
Flux.just(1,2,3).toIterable();
Stream<Integer> stream = Flux.just(1,2,3,4).toStream();
// 1、验证toIterable为阻塞
Iterable<Integer> integers = Flux.just(1, 2, 3, 4).delayElements(Duration.ofSeconds(1)).toIterable();
System.out.println("===============");
for(Integer num :integers){System.out.println(num);
}
System.out.println("===============");
//  2、我么可以做一下改进
Flux.just(1,2,3).delayElements(Duration.ofSeconds(1)).subscribe(System.out::println);
System.out.println("==========");
System.out.println("==========");
Thread.sleep(10*1000);// 3、toStream进行阻塞
Stream<Integer> integerStream = Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(1)).toStream();
System.out.println("================");
integerStream.forEach(System.out::println);
System.out.println("================");
// 4 、BlockFirst 只拿第一个,其他不处理
Integer integer = Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(1)).doOnNext(item -> System.out.println("onNext:" + item)).blockFirst();
System.out.println("==========");
System.out.println(integer);
System.out.println("==========");
Thread.sleep(10*100);//  blocklast 直到流的最后一个元素
Integer integer2 = Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(1)).doOnNext(item -> System.out.println("onNext:" + item)).blockLast();
System.out.println("==========");
System.out.println(integer2);
System.out.println("==========");Flux<Integer> integerFlux = Flux.just(1, 2, 3).delayElements(Duration.ofSeconds(1));
integerFlux.subscribe(item -> System.out.println("第一个订阅" + item));
integerFlux.subscribe(item -> System.out.println("第二个订阅" + item));
integerFlux.subscribe(item -> System.out.println("第三个订阅" + item));
Integer integer2 = integerFlux.blockFirst();
System.out.println("阻塞最后一个元素:" + integer2);
System.out.println("=================");
Thread.sleep(10*1000);

10、在序列处理时查看元素

有时,我们需要对处理管道中的每个元素或特定信号执行操作。为满足此类要求,Project Reactor提供了以下方法。
  1. doOnNext(Consumer<T>) 使我们能对 Flux 或 Mono 上的每个元素执行一些操作。
  2. doOnComplete 和 doOnError(Throwable) 可以应用在相应的事件上。
  3. doOnSubscribe(Consumer<Subscription>) 和doOnCancel(Runnable) 使我们能对订阅生命周期事件做出响应。
  4. 无论是什么原因导致的流终止, doOnTerminate(Runnable) 都会在流终止时被调用。
此外,Flux 和 Mono 提供了 doOnEach(Consumer

<Signal>) 方法,该方法处理表示响应式流领域的所有信号,包括 onSubscribe、onNext、onError 和 onComplete

考虑以下代码:

Flux.just(1,2,3).concatWith(Flux.error(new RuntimeException("手动异常")))//  .doOnEach(item -> System.out.println(item)).subscribe(item -> System.out.println("onNext:" + item),ex -> System.err.println("onError:" + ex),() -> System.out.println("处理完毕")
);

在这个例子中,我们不仅收到了所有的 onNext 信号,还收到了 onError 信号。

11、物化和非物化信号

将流中的元素封装为Signal对象进行处理。有时,采用信号进行流处理比采用数据进行流处理更有用。为了将数据流转换为信号流并再次返回,Flux 和 Mono 提供了 materialize 方法和 dematerialize 方法。

示例如下:

  Flux.just(1,2,3).delayElements(Duration.ofMillis(1000)).publishOn(Schedulers.parallel()).concatWith(Flux.error(new Exception("手动异常"))).materialize().doOnEach(item -> System.out.println(item.isOnComplete())).log().dematerialize().subscribe(System.out::println);
Thread.sleep(10*1000);

image.png

这里,在处理信号流时,doOnNext 方法不仅接收带有数据的 onNext 事件,还接收包含在Signal类中的 onComplete 事件。此方法能采用一个类型层次结构来处理 onNext、onError和 onCompete 事件。
如果我们只需要记录信号而不修改它们,那么 Reactor 提供了 log 方法,该方法使用可用的记录器记录所有处理过的信号。

二、以编程方式创建流

有时候需要一种更复杂的方法来在流中生成信号,或将对象的生命周期绑定到响应式流的生命周期。

1.1 push 和 create 工厂方法

push 工厂方法能通过适配一个单线程生产者来编程创建 Flux 实例。此方法对于适配异步、单线程、多值 API 非常有用,而无须关注背压和取消, push 方法本身包含背压和取消。

 // push
Flux<Integer> push = Flux.push(new Consumer<FluxSink<Integer>>() {@Overridepublic void accept(FluxSink<Integer> fluxSink) {// 从数据库中获取数据// FluxSink 追加到响应式流中,这样将命令处理方式,转化为响应式处理方式IntStream.range(1, 10).forEach(item -> fluxSink.next(item));}
});
push.subscribe(System.out::println);
Thread.sleep(5*1000);

我们可以转化为lambda表达式如下:

Flux<Integer> push = Flux.push(fluxSink -> {// 从数据库中获取数据// FluxSink 追加到响应式流中,这样将命令处理方式,转化为响应式处理方式IntStream.range(1, 10).forEach(item -> fluxSink.next(item));
});
push.subscribe(System.out::println);
Thread.sleep(5*1000);

push 工厂方法可以很方便地使用默认的背压和取消策略来适配异步 API。

create 工厂方法,与 push 工厂方法类似,起到桥接的作用。

该方法能从不同的线程发送事件

如下代码所示:

MyEventProcessor myEventProcessor = new MyEventProcessor();
Flux<String> bridage = Flux.create(sink -> {myEventProcessor.register(new MyEventListener<String>() {@Overridepublic void onDataChunk(List<String> chunk) {for (String s : chunk) {sink.next(s);}}@Overridepublic void processComplete() {sink.complete();}});
});
bridage.subscribe(System.out::println,ex -> System.err.println(ex),() ->System.out.println("处理完毕")
);
myEventProcessor.process();
Thread.sleep(5*1000);
static class MyEventProcessor{private MyEventListener listener;private Random random = new Random();void register(MyEventListener listener){this.listener = listener;}public void process(){while(random.nextInt(10) % 3 != 0){List<String> dataChunk = new ArrayList<>();for(int i = 0 ;i < 10;i++){dataChunk.add("data - " + i );}listener.onDataChunk(dataChunk);}listener.processComplete();;}
}
interface  MyEventListener<T>{void onDataChunk(List<T> chunk);void processComplete();
}

1.2 generate 工厂方法

generate 工厂方法旨在基于生成器的内部处理状态创建复杂序列。
它需要一个初始值和一个函数,该函数根据前一个内部状态计算下一个状态,并将 onNext 信号发送给下游订阅者。
例如,创建一个简单的响应式流来生成斐波那契(Fibonacci)数列(0,1,1,2,3,5,8,13,…)。

Flux.generate(// 通过Callable提供初始状态实例new Callable<ArrayList<Long>>() {@Overridepublic ArrayList<Long> call() throws Exception {final ArrayList<Long> longs = new ArrayList<>();longs.add(0L);longs.add(1L);return longs;}}, // 负责斐波拉契数列// 函数第一个参数数据、函数第二个参数类型 、返回值new BiFunction<ArrayList<Long>, SynchronousSink<Long>, ArrayList<Long>>() {@Overridepublic ArrayList<Long> apply(ArrayList<Long> longs, SynchronousSink<Long> sink) {Long aLong = longs.get(longs.size() - 1);Long aLong1 = longs.get(longs.size() - 2);sink.next(aLong);longs.add(aLong + aLong1);return longs;}}).delayElements(Duration.ofMillis(500)).take(10).subscribe(System.out::println);
Thread.sleep(5000);

我们使用lambda表达式处理:

Flux.generate(// 通过Callable提供初始状态实例() -> {final ArrayList<Long> longs = new ArrayList<>();longs.add(0L);longs.add(1L);return longs;}, // 负责斐波拉契数列// 函数第一个参数数据、函数第二个参数类型 、返回值(BiFunction<ArrayList<Long>, SynchronousSink<Long>, ArrayList<Long>>) (longs, sink) -> {Long aLong = longs.get(longs.size() - 1);Long aLong1 = longs.get(longs.size() - 2);sink.next(aLong);longs.add(aLong + aLong1);return longs;}).delayElements(Duration.ofMillis(500)).take(10).subscribe(System.out::println);
Thread.sleep(5000);

状态还可以记录为二元组:

Flux.generate(// 通过Callable提供初始状态实例() -> Tuples.of(0L,1L), // 负责斐波拉契数列// 函数第一个参数数据、函数第二个参数类型 、返回值(state, sink) -> {System.out.println("生成的数字:" + state.getT2());sink.next(state.getT1());long nextValue  = state.getT1() + state.getT2();return Tuples.of(state.getT2(),nextValue);}).delayElements(Duration.ofMillis(500)).take(10).subscribe(System.out::println);
Thread.sleep(5000);

1.3 将 disposable 资源包装到响应式流中

using 工厂方法能根据一个 disposable 资源创建流。它在响应式编程中实现了 try-with-resources 方法。

假设我们需要包装一个阻塞 API,而该 API 使用以下有如下表示:

static class Connection implements AutoCloseable {private final Random rnd = new Random();static Connection newConnection() {System.out.println("创建Connection对象");return new Connection();}public Iterable<String> getData() {if (rnd.nextInt(10) < 3) {throw new RuntimeException("通信异常");}return Arrays.asList("数据1", "数据2");}// close方法可以释放内部资源,并且应该始终被调用,即使在getData执行期间发生错误也是如此。@Overridepublic void close() {System.out.println("关闭Connection连接");}
}

使用命令式方法,我们可以使用以下代码从连接接收数据:

public static void main(String[] args) throws InterruptedException {try (Connection connection = Connection.newConnection()) {connection.getData().forEach(data -> System.out.println("接收的数据:" +data));} catch (Exception e) {System.err.println("错误信息:" + e);}
}

有相同作用的响应式代码如下所示

Flux.using(Connection::newConnection,connection -> Flux.fromIterable(connection.getData()),Connection::close
).subscribe(data -> System.out.println("onNext接收到数据:" + data),ex -> System.err.println("onError接收到的异常信息:" +ex),() -> System.out.println("处理完毕")
);

连接的生命周期与流的生命周期绑定。

操作符还可以在通知订阅者流终止之前或之后选择是否应该进行清除动作。

三、错误处理

onError 信号是响应式流规范的一个组成部分,一种将异常传播给可以处理它的用户。但是,如果最终订阅者没有为 onError 信号定义处理程序,那么 onError 抛异常。此外,响应式流的语义定义了 onError 是一个终止操作,该操作之后响应式流会停止执行。
此时,我们可能采取以下策略中的一种做出不同响应:

  1. 为 subscribe 操作符中的 onError 信号定义处理程序。
  2. 定义一个在发生错误时重新执行的响应式工作流。如果源响应序列发出错误信号,那么retry 操作符会重新订阅该序列。
  3. 通过 onErrorResume 操作符捕获异常并执行备用工作流。
  4. 通过 onErrorReturn 操作符捕获一个错误,并用一个默认静态值或一个从异常中计算出的值替换它。
  5. 通过 onErrorMap 操作符捕获异常并将其转换为另一个异常来更好地表现当前场景。

1、onError

//onError
Flux.from(new Publisher<String>() {@Overridepublic void subscribe(Subscriber<? super String> s) {s.onError(new RuntimeException("手动异常"));}// }).subscribe(System.out::println);
}).subscribe(System.out::println,System.err::println);

2、retry

  private static Random random = new Random();private static Flux<String> recommendedBooks(String userId){return Flux.defer(()->{if(random.nextInt(10) < 7){return Flux.<String>error(new RuntimeException("err"))// 整体向后推移时间.delaySequence(Duration.ofMillis(100));}else{return Flux.just("西游记","红楼梦").delayElements(Duration.ofMillis(10));}}).doOnSubscribe(item -> System.out.println("请求:" + userId));}
  private static CountDownLatch latch = new CountDownLatch(1);public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user).retry(3))//这里最多会调用四次,第一次正常调用 后三次进行重试.subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

3、onErrorResume

 public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user)).onErrorResume(error -> Flux.just("三国演义"))// 异常捕获返回备用的流.subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

4、onErrorReturn

 public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user)).onErrorReturn("水浒传")// 异常捕获返回备用的流.subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

5、onErrorMap

 public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user)).onErrorMap(throwable -> {if(throwable.getMessage().equals("err")){return new Exception("业务异常");}return new Exception("未知异常");}).subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

总而言之,Project Reactor 提供了丰富的工具集,可以帮助处理异常情况,从而提高应用程序的回弹性。

错误处理

onError 信号是响应式流规范的一个组成部分,一种将异常传播给可以处理它的用户。但是,如果最终订阅者没有为 onError 信号定义处理程序,那么 onError 抛异常。
此外,响应式流的语义定义了 onError 是一个终止操作,该操作之后响应式流会停止执行。
此时,我们可能采取以下策略中的一种做出不同响应:

  1. 为 subscribe 操作符中的 onError 信号定义处理程序。
  2. 定义一个在发生错误时重新执行的响应式工作流。如果源响应序列发出错误信号,那么retry 操作符会重新订阅该序列。
  3. 通过 onErrorResume 操作符捕获异常并执行备用工作流。
  4. 通过 onErrorReturn 操作符捕获一个错误,并用一个默认静态值或一个从异常中计算出的值替换它。
  5. 通过 onErrorMap 操作符捕获异常并将其转换为另一个异常来更好地表现当前场景。

1、onError

//onError
Flux.from(new Publisher<String>() {@Overridepublic void subscribe(Subscriber<? super String> s) {s.onError(new RuntimeException("手动异常"));}// }).subscribe(System.out::println);
}).subscribe(System.out::println,System.err::println);

2、retry

  private static Random random = new Random();private static Flux<String> recommendedBooks(String userId){return Flux.defer(()->{if(random.nextInt(10) < 7){return Flux.<String>error(new RuntimeException("err"))// 整体向后推移时间.delaySequence(Duration.ofMillis(100));}else{return Flux.just("西游记","红楼梦").delayElements(Duration.ofMillis(10));}}).doOnSubscribe(item -> System.out.println("请求:" + userId));}
  private static CountDownLatch latch = new CountDownLatch(1);public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user).retry(3))//这里最多会调用四次,第一次正常调用 后三次进行重试.subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

3、onErrorResume

 public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user)).onErrorResume(error -> Flux.just("三国演义"))// 异常捕获返回备用的流.subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

4、onErrorReturn

 public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user)).onErrorReturn("水浒传")// 异常捕获返回备用的流.subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}

5、onErrorMap

 public static void main(String[] args) throws InterruptedException {Flux.just("user-1000").flatMap(user -> recommendedBooks(user)).onErrorMap(throwable -> {if(throwable.getMessage().equals("err")){return new Exception("业务异常");}return new Exception("未知异常");}).subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();}
总而言之,Project Reactor 提供了丰富的工具集,可以帮助处理异常情况,从而提高应用程序的回弹性。

四、背压处理

尽管响应式流规范要求将背压构建到生产者和消费者之间的通信中,但这仍然可能使消费者溢出。一些消费者可能无意识地请求无界需求,然后无法处理生成的负载。另一些消费者则可能对传入消息的速率有严格的限制。例如,数据库客户端每秒不能插入超过 1000条记录。在这种情况下,事件批处理技术可能有所帮助。
可以通过以下方式配置流以处理背压情况:

  1. onBackPressureBuffer 操作符会请求无界需求并将返回的元素推送到下游。如果下游消费者无法跟上,那么元素将缓冲在队列中。
  2. onBackPressureDrop 操作符也请求无界需求(Integer.MAX_VALUE)并向下游推送数据。如果下游请求数量不足,那么元素会被丢弃。自定义处理程序可以用来处理已丢弃的元素。
  3. onBackPressureLast 操作符与 onBackPressureDrop 的工作方式类似。只是会记住最近收到的元素,并在需求出现时立即将其推向下游。
  4. onBackPressureError 操作符在尝试向下游推送数据时请求无界需求。如果下游消费者无法跟上,则操作符会引发错误。

管理背压的另一种方法是使用速率限制技术

onBackPressureBuffer

public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(1);Flux.range(1,1000).delayElements(Duration.ofMillis(10)).onBackpressureBuffer(600).delayElements(Duration.ofMillis(100)).subscribe(System.out::println,ex ->{System.out.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});latch.await();System.out.println("main结束");
}

onBackPressureDrop

Flux.range(1,1000).delayElements(Duration.ofMillis(10)).onBackpressureDrop().delayElements(Duration.ofMillis(100)).subscribe(System.out::println,ex ->{System.out.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});
latch.await();
System.out.println("main结束");

onBackPressureLast

CountDownLatch latch = new CountDownLatch(1);
Flux.range(1,1000).delayElements(Duration.ofMillis(10)).onBackpressureLatest().delayElements(Duration.ofMillis(100)).subscribe(System.out::println,ex ->{System.out.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});
latch.await();
System.out.println("main结束");

onBackPressureError

CountDownLatch latch = new CountDownLatch(1);
Flux.range(1,1000).delayElements(Duration.ofMillis(10)).onBackpressureError().delayElements(Duration.ofMillis(100)).subscribe(System.out::println,ex ->{System.out.println(ex);latch.countDown();},() ->{System.out.println("处理完毕");latch.countDown();});
latch.await();
System.out.println("main结束");

五、热数据流和冷数据流

冷发布者行为方式:无论订阅者何时出现,都为该订阅者生成所有序列数据,

没有订阅者就不会生成数据

 Flux<String> coldPublisher = Flux.defer(()->{System.out.println("生成数据");return Flux.just(UUID.randomUUID().toString());});System.out.println("尚未生成数据");coldPublisher.subscribe(e -> System.out.println("onNext:" + e));coldPublisher.subscribe(e -> System.out.println("onNext:" + e));System.out.println("为两次订阅生成两次数据");

每当订阅者出现时都会有一个新序列生成,而这些语义可以代表 HTTP请求。
热发布者中的数据生成不依赖于订阅者而存在。因此,热发布者可能在第一个订阅者出现之前开始生成元素。
这种语义代表数据广播场景。例如,一旦股价发生变化,热发布者就可以向其订阅者广播有关当前股价的更新。
但是,当订阅者到达时,它仅接收未来的价格更新,而不接受先前价格历史。

多播流元素

通过响应式转换将冷发布者转变为热发布者。如,一旦所有订阅者都准备好生成数据,希望在几个订阅者之间共享冷处理器的结果。同时,我们又不希望为每个订阅者重新生成数据。

示例如下:

Flux<Integer> source = Flux.range(0,3).doOnSubscribe(s -> System.out.println("对冷发布者的新订阅票据:" + s));
ConnectableFlux<Integer> conn = source.publish();
conn.subscribe(item -> System.out.println("[subscriber 1] onNext:" + item));
conn.subscribe(item -> System.out.println("[subscriber 2] onNext:" + item));
System.out.println("所有定于这都准备建立连接");
conn.connect();

可以看到,冷发布者收到了订阅,只生成了一次数据项。但是,两个订阅者都收到了整个事件集合。

缓存流元素

使用 ConnectableFlux 可以轻松实现不同的

数据缓存策略

cache 操作符使用 ConnectableFlux ,因此它的主要附加值是它所提供的一个流式而直接的API。
可以调整缓存所能容纳的数据量以及每个缓存项的到期时间。

示例代码:

Flux<Integer> source = Flux.range(0,5).doOnSubscribe(s -> System.out.println("冷发布者的新订阅数据"));
Flux<Integer> cacheSource = source.cache(Duration.ofMillis(1000));
cacheSource.subscribe(item -> System.out.println("[subscribe 1] on Next:" +item));
cacheSource.subscribe(item -> System.out.println("[subscribe 2] on Next:" +item));
Thread.sleep(1200);
cacheSource.subscribe(item -> System.out.println("[subscribe 3] on Next:" +item));

前两个订阅者共享第一个订阅的同一份缓存数据。然后,在一定延迟之后,由于第三个订阅者无法获取缓存数据,因此一个针对冷发布者的新订阅被触发了。最后,即使该数据不来自缓存,第三个订阅者也接收到了所需的数据。

共享流元素

我们可以使用 ConnectableFlux 向几个订阅者多播事件。但是需要等待订阅者出现才能开始处理。
share 操作符可以将冷发布者转变为热发布者。该操作符会为每个新订阅者传播订阅者尚未错过的事件。

示例代码:

Flux<Integer> source = Flux.range(0, 5).delayElements(Duration.ofMillis(100)).doOnSubscribe(s -> System.out.println("冷发布者新的订阅票据"));
Flux<Integer> shareSource = source.share();
shareSource.subscribe(item -> System.out.println("subscribe 1 onNext:" + item));
Thread.sleep(400);
shareSource.subscribe(item -> System.out.println("subscribe 2 onNext:" + item));
Thread.sleep(10*1000);

在前面的代码中,共享了一个冷发布流,该流以每 100 毫秒为间隔生成事件。然后,经过一些延迟,一些订阅者订阅了共享发布者。
第一个订阅者从第一个事件开始接收,而第二个订阅者错过了在其出现之前所产生的事件(S2 仅接收到事件 3 和事件 4

六、处理时间

响应式编程是异步的,因此它本身就假定存在时序。

基于 Project Reactor,可以使用 interval 操作符生成基于一定持续时间的事件,使用delayElements 操作符生成延迟元素,并使用 delaySequence 操作符延迟所有信号。

Reactor 的 API 使你能对一些与时间相关的事件做出响应, timestamp 操作符用于输出元素的时间戳, timeout 操作符用于指定消息时间间隔的大小。与 timestamp 类似, elapsed 操作符测量与上一个事件的时间间隔。

1、interval 操作符

// interval
// 1、指定生成元素的时间间隔
Flux.interval(Duration.ofMillis(100)).subscribe(item ->{System.out.println(Thread.currentThread().getName() +"========" + item);});
System.out.println(Thread.currentThread().getName());// 2、 第一个参数指定发送事件于订阅时间的时间间隔
// 第二个擦书:指定生成序列元素的时间间隔
Flux.interval(Duration.ofSeconds(20),Duration.ofMillis(300)).subscribe(System.out::println);Thread.sleep(5 *1000);
System.out.println("结束");

2、delayElements 操作符

Flux.range(1,1000)// 指定元素之间的时间间隔.delayElements(Duration.ofSeconds(1)).subscribe(item ->{System.out.println(Thread.currentThread().getName() + "====" + item);});Thread.sleep(5 *1000);
System.out.println("结束");

3、delaySequence 操作符

Flux.range(1,1000)// 指定元素之间的时间间隔.delaySequence(Duration.ofSeconds(5)).subscribe(item ->{System.out.println(Thread.currentThread().getName() + "====" + item);});Thread.sleep(10 *1000);
System.out.println("结束");

4、timeout 操作符

Random random = new Random();
CountDownLatch latch = new CountDownLatch(1);
// 指定时间间隔大于我们的timeout时间,就抛异常
Flux.interval(Duration.ofMillis(300)).timeout(Duration.ofMillis(random.nextInt(20) + 290)).subscribe(System.out::println,ex -> {System.err.println(ex);latch.countDown();}
);
latch.await(10, TimeUnit.SECONDS);

5、timestamp 操作符

// 为我们响应式流里面增加一个响应式时间戳,时间戳和元素会以二元组形式在流里面传递
Flux.interval(Duration.ofMillis(300)).timestamp().subscribe( item ->{Long timeStamp = item.getT1();Long element = item.getT2();String result = element + "的时间戳" + timeStamp;System.out.println(result);});
Thread.sleep(5000);

6、elapsed 操作符

// elapsed
Flux.interval(Duration.ofMillis(300)).elapsed().subscribe(item ->{Long interval = item.getT1();Long element = item.getT2();String result = element + "与上一个元素的时间间隔" + interval + "ms";System.out.println(result);}
);
Thread.sleep(5000);

但是我们看到这个时间间隔并不准确,所以说不要在 Reactor 库中要求太精确的时间(实时)间隔

七、组合和转换响应式流

当我们构建复杂的响应式工作流时,通常需要在几个不同的地方使用相同的操作符序列。

transform 操作符,可以将这些常见的部分提取到单独的对象中,并在需要时重用它们。

transform 操作符,可以增强流结构本身。

public static void main(String[] args) {Function<Flux<String>,Flux<String>> logUserInfo = stream ->stream.index().doOnNext(tp  -> System.out.println("[" + tp.getT1() +"] user:" + tp.getT2())).map(Tuple2::getT2);Flux.range(1000,3).map(i -> "user - " + i).transform(logUserInfo).subscribe(e -> System.out.println("onNext:" + e));}

transform 操作符仅在流生命周期的组装阶段更新一次流行为,可以在响应式应用程序中实现代码重用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/377243.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「豆包Marscode体验官」我用豆包Marscode画数据大屏

认识豆包Marscode 豆包 MarsCode IDE 是一个 AI 原生的云端集成开发环境&#xff08;IDE&#xff09;。内置的 AI 编程助手和开箱即用的开发环境让我们可以更加专注于各种项目的开发。豆包 MarsCode 编程助手&#xff0c;具备以智能代码补全为代表的 AI 功能。支持了多种编程语…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(十二)-无人机群在物流中的应用

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

解读网传《深圳IT圈⭕新解读八小时工作制》

网传深圳IT圈的新解读八小时工作制 工作时间安排&#xff1a; 10:00-12:0014:00-18:0019:00-21:00 初看&#xff1a;有惊喜 上午开始时间晚&#xff1a;相对于传统的9点开始&#xff0c;这种安排允许员工有更多的早晨时间&#xff0c;可以用来休息或处理个人事务。下午和晚上分…

Amazon EC2 部署Ollama + webUI

最近和同事闲聊&#xff0c;我们能不能内网自己部署一个LLM&#xff0c;于是便有了Ollama webUI的尝试 对于Linux&#xff0c;使用一行命令即可 curl -fsSL https://ollama.com/install.sh | shollama --help Large language model runnerUsage:ollam…

架构设计-NX的二次开发API架构设计介绍

1.与整体的关系 2.API设计目标 能够允许用户访问NX的所有UI工具组件&#xff0c;二次开发用户能够编写外观和运行行为类似NX的应用程序。能够允许用户直接访问NX数据模型即使底层数据结构和功能实现发生很大变化&#xff0c;API接口保持稳定&#xff0c;不会影响上层用户。 3…

智能家居开发新进展:乐鑫 ESP-ZeroCode 与亚马逊 ACK for Matter 实现集成

日前&#xff0c;乐鑫 ESP-ZeroCode 与亚马逊 Alexa Connect Kit (ACK) for Matter 实现了集成。这对智能家居设备制造商来说是一项重大进展。开发人员无需编写固件或开发移动应用程序&#xff0c;即可轻松设计符合 Matter 标准的产品。不仅如此&#xff0c;开发者还可以在短短…

网络协议 — Keepalived 高可用方案

目录 文章目录 目录Keepalived 是实现了 VRRP 协议的软件Keepalived 的软件架构VRRP StackCheckersKeepalived 的配置Global configurationvrrp_scriptVRRP Configurationvrrp synchroization groupvrrp instancevirtual ip addressesvirtual routesLVS Configurationvirtual_s…

知识图谱与LLMs:实时图分析(通过其关系的上下文理解数据点)

大型语言模型 (LLM) 极大地改变了普通人获取数据的方式。不到一年前&#xff0c;访问公司数据需要具备技术技能&#xff0c;包括熟练掌握各种仪表板工具&#xff0c;甚至深入研究数据库查询语言的复杂性。然而&#xff0c;随着 ChatGPT 等 LLM 的兴起&#xff0c;随着所谓的检索…

不常用的第三方服务集成

1.ldap 1.1.ldap服务搭建 docker方式搭建:包含了ldap服务和ldap admin图形化界面服务 参考ldap服务:http://127.0.0.1:81 用户名:CN=admin,DC=ldap,DC=com 密码:123456 docker-compose.yml文件内容如下 version: 3services:ldap:image: osixia/openldap:latestcontainer…

0基础学会在亚马逊云科技AWS上利用SageMaker、PEFT和LoRA高效微调AI大语言模型(含具体教程和代码)

项目简介&#xff1a; 小李哥今天将继续介绍亚马逊云科技AWS云计算平台上的前沿前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS上的AI软甲开发最佳实践&#xff0c;并应用到自己的日常工作里。本次介绍的是如何在Amazon SageMaker上…

【qt】TCP客户端如何断开连接?

disconnectFromHost() 来关闭套接字,断开连接. 当我们关闭窗口时,也需要断开连接. 需要重写关闭事件 如果当前的套接字状态是连接上的,我们就可以来断开连接. 运行结果:

SSM框架学习笔记(仅供参考)

&#xff08;当前笔记简陋&#xff0c;仅供参考&#xff09; 第一节课&#xff1a; &#xff08;1&#xff09;讲述了Spring框架&#xff0c;常用jar包&#xff0c;以及框架中各个文件的作用 &#xff08;2&#xff09;演示了一个入门程序 &#xff08;3&#xff09;解释了…

前端项目本地的node_modules直接上传到服务器上无法直接使用(node-sasa模块报错)

跑 jekins任务的服务器不能连接外网下载依赖包&#xff0c;就将本地下载的 node_modules直接上传到服务器上&#xff0c;但是运行时node-sass模块报错了ERROR in Missing binding /root/component/node_modules/node-sass/vendor/linux-x64-48/binding.node >> 报错信息类…

不会编程怎么办?量化交易不会编程可以使用吗?

量化交易使用计算机模型程序代替人工进行交易&#xff0c;一般需要投资者自己编写程序建模&#xff0c;然后回测无误之后再进行实盘交易&#xff0c;那么不会编程的投资者能使用量化软件进行量化交易吗&#xff1f; 不会编程使用量化软件有两种方法 一种是请人代写代码&#x…

浅谈后置处理器之JSON提取器

浅谈后置处理器之JSON提取器 JMeter 的 JSON 提取器&#xff08;JSON Extractor&#xff09;是一个强大的后置处理器&#xff0c;它允许用户从HTTP响应、数据库查询或其他类型的响应中提取JSON数据&#xff0c;并将这些数据存储为变量&#xff0c;以便在后续的请求中重用。这对…

LabVIEW人工模拟肺控制系统开发

开发了一种创新的主被动一体式人工模拟肺模型&#xff0c;通过LabVIEW开发的上位机软件&#xff0c;实现了步进电机驱动系统的精确控制和多种呼吸模式的模拟。该系统不仅能够在主动呼吸模式下精确模拟快速呼吸、平静呼吸和深度呼吸&#xff0c;还能在被动模式下通过PID控制实现…

LeetCode加油站(贪心算法/暴力,分析其时间和空间复杂度)

题目描述 一.原本暴力算法 最初的想法是&#xff1a;先比较gas数组和cost数组的大小&#xff0c;找到可以作为起始点的站点(因为如果你起始点的油还不能到达下一个站点&#xff0c;就不能作为起始点)。当找到过后&#xff0c;再去依次顺序跑一圈&#xff0c;如果剩余的油为负数…

【代码随想录】【算法训练营】【第64天】 [卡码117]软件构建 [卡码47]参加科学大会

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 卡码网。 day 64&#xff0c;周三&#xff0c;继续ding~ 题目详情 [卡码117] 软件构建 题目描述 卡码117 软件构建 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 [卡码…

GO channel 学习

引言 单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。 虽然可以使用共享内存进行数据交换&#xff0c;但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性&#xff0c;必须使用互斥量对内存进行加锁&#…

喰星云·数字化餐饮服务系统 多处 SQL注入漏洞复现

0x01 产品简介 喰星云数字化餐饮服务系统是一款专为餐饮企业设计的综合性管理软件,旨在通过信息化手段提升餐饮企业的运营效率、降低运营成本,并实现数据驱动的决策管理。该系统包括供应链管理、财务管理、巡店管理、人力资源管理等多个模块,可全面覆盖餐饮企业的日常运营需…