自学内容网 自学内容网

Spring webflux

在目前的jdk 已经迭代22 虚拟线程(携程)来说 ,上下文的切换的压力得到优化,Spring webflux的热度也慢慢不温不火,但是其中的设计思想还是值得了解的

Spring WebFlux

1. 什么是 Spring WebFlux?

Spring WebFlux 是 Spring Framework 5 引入的非阻塞、响应式编程框架,它是基于异步 I/O 模型构建的。WebFlux 提供了一个事件驱动的响应式编程模型,可以处理大量并发请求而不会被 I/O 操作阻塞。WebFlux 可以运行在支持 Reactive Streams 的服务器(如 Netty)上,也可以在支持 Servlet 3.1+ 规范的容器(如 Tomcat、Jetty)上运行。

2. Spring WebFlux 与 Spring MVC 的主要区别

  • 阻塞 vs. 非阻塞

    • Spring MVC 是阻塞 I/O,每个请求都需要一个线程来处理。如果线程等待 I/O 操作(如数据库查询),它会被阻塞。
    • Spring WebFlux 使用非阻塞 I/O,线程不需要等待 I/O 操作完成,发出 I/O 请求后,线程可以释放去处理其他任务,当 I/O 操作完成时再回调处理结果。
  • 同步 vs. 异步

    • Spring MVC 是同步的,每个请求会在一个线程上被处理到底,直到完成。
    • Spring WebFlux 是异步的,任务被分为多个步骤,且不需要在同一个线程上执行。

这里可能一直对bio,nio,aio等问题困扰,这里就要说一下,底层使用到的是netty这种bio框架,所以需要先了解

BIO模型和多线程

即使在响应式编程异步 I/O的模型中,本质上确实涉及多线程的概念,但它与传统的基于阻塞 I/O (BIO) 的多线程模型有显著区别。响应式编程和异步 I/O 通过一种更高效的方式管理线程和资源,从而避免了传统多线程模型的高开销和低效率。

BIO(阻塞 I/O)和多线程

在基于阻塞 I/O 的传统服务器模型(如 Spring MVC),每个请求会分配一个独立的线程。这个线程在等待 I/O 操作(例如数据库查询或外部 API 调用)时会进入阻塞状态,直到操作完成。在这种模型下,如果有多个请求同时到来,服务器需要创建或分配相应数量的线程。这种方式虽然简单直观,但存在一些问题:

  1. 高并发下资源浪费:大量线程会占用服务器的内存和 CPU 资源,特别是当线程在等待 I/O 时,资源仍然被锁定但没有被有效利用。
  2. 线程上下文切换开销:操作系统需要花费大量时间在不同的线程之间切换,这种线程上下文切换(context switching)开销在高并发环境下会变得非常明显,导致性能下降。
异步 I/O 和响应式模型的多线程

异步 I/O(如 Spring WebFlux 的实现)中,尽管仍然使用多线程,但其运作方式大大优化,避免了传统阻塞模型中的资源浪费和高开销。异步 I/O 主要通过 事件驱动回调机制 来处理请求,从而达到高并发下的高效性能。

异步 I/O 的工作原理
  1. 事件循环线程模型:在异步 I/O 模型中,只有少量的线程(通常是 事件循环线程池)用于处理所有 I/O 操作。当请求到达时,系统不会为每个请求分配一个线程,而是将请求放入事件循环中,由少数线程来监控和处理。线程只负责启动 I/O 操作,并在 I/O 操作完成时,通过回调或事件触发进一步的处理。

  2. 非阻塞式 I/O:当系统发起一个 I/O 请求(如数据库查询或网络请求)时,线程不会等待该请求完成,而是将操作挂起,并继续处理其他请求或任务。当 I/O 操作完成时,操作系统会通知应用程序(通过回调或事件),并在事件循环中继续处理这个任务的剩余部分。

  3. 背压和资源控制:在响应式编程中,通常有背压(Backpressure)机制,它允许消费者(请求处理代码)根据自身的处理能力,来调节数据流的速率,防止服务器资源过载。

关键差异:多线程 vs 事件驱动
特性BIO(传统多线程模型)异步 I/O(响应式模型)
线程分配每个请求一个线程少量线程处理大量请求
I/O 操作阻塞等待非阻塞,异步处理
资源消耗高,线程在等待 I/O 时浪费资源低,线程被释放,继续处理其他任务
并发处理线程数量受限,容易导致资源瓶颈高效利用少量线程,适合高并发场景
线程上下文切换高频上下文切换,开销大减少上下文切换,开销小
响应性处理时间依赖于线程是否可用和 I/O 阻塞响应更快,避免线程被长时间占用
响应式编程中的多线程

尽管异步 I/O 和响应式编程模型更高效地使用了线程,但多线程仍然是不可避免的。只是:

  1. 线程数少:响应式系统通常使用更少的线程,比如基于 事件循环Reactor NettyVert.x 通常只有固定数量的 I/O 线程,不会为每个请求创建新的线程。

  2. 线程不会被阻塞:线程不再是处理 I/O 操作的核心部分,而是负责调度和管理事件流。一旦发起 I/O 操作,线程会被释放出来,继续处理其他任务。

  3. 线程池管理:响应式系统通常会有不同的线程池来处理不同类型的任务。比如,I/O 操作可能由一个线程池处理,而 CPU 密集型任务由另一个线程池处理。这种分离使得系统在处理不同任务时更高效。

  4. 异步和回调:响应式编程强调 异步,操作通常通过回调链或响应式流(如 MonoFlux)来完成,而不是通过线程阻塞来等待结果。这样可以充分利用服务器资源,即使在高并发情况下也能保持高效。

因此,异步 I/O 和响应式编程中的“多线程”不同于传统的多线程模型,它依赖于少量线程的高效利用异步非阻塞的设计理念,能够在处理大量并发请求时保持高效的性能。

  • 基于 Servlet vs. 基于 Reactive Streams
    • Spring MVC 依赖于 Servlet API,主要是传统的阻塞 I/O 模型。
    • Spring WebFlux 依赖 Reactive Streams 规范,基于 Reactor 框架。

3. 异步 I/O 与非阻塞 I/O

要理解 Spring WebFlux 的核心异步和非阻塞编程模型,首先需要理解一些底层概念。

3.1 阻塞 I/O (Blocking I/O)

在传统的阻塞 I/O 模型中,一个线程发起一个 I/O 操作(如读取文件或数据库查询),它会被阻塞,即必须等待 I/O 操作完成,才能继续执行后续的代码逻辑。这个线程在等待期间无法处理其他任务。

缺点

  • 线程会因为等待 I/O 操作而浪费资源,系统的并发处理能力受限于线程池大小。
  • 阻塞模型在高并发场景下不够高效,因为每个请求都会占用一个线程,而线程是稀缺资源。
3.2 非阻塞 I/O (Non-blocking I/O)

在非阻塞 I/O 模型中,线程发起 I/O 操作后不会被阻塞,而是立即被释放。I/O 操作在后台继续进行,线程可以继续执行其他任务。当 I/O 操作完成时,系统会通过回调机制事件通知来告知线程操作已完成,线程再继续处理操作结果。

非阻塞 I/O 具体步骤

  1. 发起 I/O 请求:线程发起 I/O 操作(如数据库查询),不会等待操作完成,而是继续处理其他请求。
  2. 释放线程:该线程不会因为 I/O 被占用,而是被释放回到线程池,可以处理其他任务。
  3. I/O 完成回调:当 I/O 操作完成时,操作系统或 I/O 子系统会发出事件通知,告知线程任务完成。线程会接收该通知并处理后续操作。
3.3 异步 I/O (Asynchronous I/O)

异步 I/O 通常与非阻塞 I/O 配合使用。异步 I/O 模型的特点是,I/O 操作不会在发起时阻塞线程,而是通过回调或事件机制来异步处理任务。

异步 I/O 和非阻塞 I/O 的区别

  • 非阻塞 I/O 强调的是线程不需要等待 I/O 操作的结果,可以继续执行其他任务。
  • 异步 I/O 强调的是任务之间的非线性处理方式,任务的不同阶段通过回调机制来协同完成。

所以其实说简单一点就是,

传统的mvc服务器会每次收到一个请求就新建一个线程,来处理这个请求,而Spring webflux这种异步服务器类似线程池,web服务器会提前维护一批线程,用多路复用的方式,每次到达的请求就采用之前维护的线程进行处理请求,这样就不会每次一个线程就处理资源,

4. 核心机制: 线程池、事件驱动与多路复用

WebFlux 和其他基于异步 I/O 的服务器模型确实与 线程池 有很大相似之处,它们通过维护一组固定数量的线程(如 事件循环线程池)来处理大量的请求,而不为每个请求单独创建新线程。

核心理解:多路复用 + 线程池
  1. 事件循环和线程池

    • 事件循环模型:服务器通常会维护一小部分线程,这些线程负责处理所有的 I/O 事件。这个机制类似于操作系统中的 多路复用(I/O Multiplexing),如 Linux 下的 epollselect,它们可以监听多个 I/O 操作,一旦有数据可用或操作完成,系统就会通知事件循环线程去处理。
    • 线程池的概念:响应式模型的确会提前创建并维护一组有限的线程,并通过调度机制来管理这些线程处理所有的请求。这与传统的线程池概念非常相似,每个请求并不会占用一个独立的线程,而是由事件驱动机制和少量的线程协同处理。
  2. 多路复用的工作原理

    • 当一个请求到达时,服务器并不会立刻创建一个新线程来处理该请求,而是将请求注册到事件循环中。如果该请求涉及 I/O 操作(如文件读写、数据库查询等),事件循环会在 I/O 完成时通知相应的线程去处理这个任务,而这个线程在等待期间可以继续处理其他请求。
    • 通过这种 多路复用 的方式,服务器不需要每个请求都占用一个独立的线程,从而减少了线程的上下文切换和阻塞,极大地提升了系统的并发处理能力。
为什么不会造成性能瓶颈?

虽然线程池和事件循环的线程是固定数量的,但性能不会受到太大影响,原因在于以下几个关键点:

  1. 非阻塞 I/O

    • 在线程池模型中,每个线程并不会因为 I/O 操作而被阻塞。传统的阻塞模型会让线程等待 I/O 操作(如网络请求或数据库查询)完成,但在非阻塞 I/O 模型中,线程在发起 I/O 请求后会立即被释放,去处理其他任务。
    • 只有在 I/O 操作完成时,系统才会通知线程去处理后续逻辑。因此,即便线程数量是固定的,它们能在同一时间处理大量并发请求。
  2. 少量线程处理大量任务

    • 因为线程在异步模型中不需要等待 I/O 操作的结果,所以一个线程可以同时处理多个请求的不同阶段。假设有 1000 个并发请求,其中大多数需要等待数据库或外部服务返回数据,那么这 1000 个请求并不会需要 1000 个线程来处理,而可能只需要 10-20 个线程。
    • 通过事件驱动模型,线程只在需要处理业务逻辑时才被调用,避免了资源浪费。
  3. 背压(Backpressure)机制

    • 响应式系统有一套背压机制来防止请求过载。假设请求量瞬间激增,系统可以通过背压控制数据流的速度,保证系统不会因为请求过多而崩溃。线程池中空闲的线程会按需分配,避免了线程过载或过多的并发请求导致性能下降。
  4. 减少线程上下文切换

    • 传统的多线程模型中,频繁的线程创建和销毁,以及大量的上下文切换,会对系统性能造成较大影响。非阻塞模型通过少量线程去处理任务,减少了线程切换的频率,这样 CPU 资源更多用于处理实际业务逻辑,而不是在线程调度和切换上浪费。
响应时间不会增加的原因:
  1. 高效的线程利用率
    因为线程不被 I/O 操作阻塞,所以即使线程数量有限,每个线程的利用率会非常高。当一个线程等待 I/O 操作完成时,它可以去处理其他请求的业务逻辑。这样可以提高线程的并发处理能力,减少系统在高并发情况下的响应延迟。

  2. 异步回调机制
    通过异步编程模型,I/O 操作的完成不再依赖于线程阻塞,而是通过回调机制通知相关线程继续处理剩余任务。这种模式下,系统可以快速处理请求的部分业务逻辑,并通过异步方式等待 I/O 操作完成。最终的响应时间是由整体的异步任务完成时间决定的,而不是由单个线程的阻塞状态决定。

  3. 事件驱动响应更快
    非阻塞 I/O 模型通过事件驱动方式处理请求,因此在高并发的情况下,服务器能迅速对请求做出反应,而不会因为线程资源耗尽导致响应时间增加。通过优化的 I/O 多路复用机制,系统可以快速侦听和处理大量请求,而不会因为线程数量有限而延迟响应。

总结

WebFlux 的确通过 线程池多路复用 的方式来处理请求,避免了传统每个请求都创建新线程的做法。虽然维护的线程数量是固定的,但由于采用了 非阻塞异步事件驱动 的机制,线程并不会因为等待 I/O 而被阻塞,从而能够同时处理多个请求。这样一来,即使线程数量有限,系统仍然能够高效处理高并发请求,保持良好的响应时间。

在理解 WebFlux 时,最核心的点是事件驱动模型I/O 多路复用,以及它如何通过少量线程处理大量并发请求。

4.3 线程池

尽管 WebFlux 使用的是非阻塞 I/O,但它仍然依赖一个小型线程池来处理业务逻辑。每个线程不需要等待 I/O 操作的完成,可以并发地处理多个请求,从而减少线程上下文切换的开销。

5. Spring WebFlux 编程模型

5.1 核心类:Mono 和 Flux

在 WebFlux 中,处理请求的结果通常以两种响应类型表示:MonoFlux
webflux构建的异步服务器中,返回的响应都是通过flux和mono包裹的异步序列

  • Mono:表示 0 或 1 个元素的异步序列,常用于处理单一结果。
  • Flux:表示 0 到 N 个元素的异步序列,常用于处理多个结果。

示例

/*
包裹像响应结构题体
*/
@GetMapping
public Mono<String> getHelloMessage() {
    return Mono.just("Hello, WebFlux!");
}
@GetMapping("2")
public Flux<String> getNames() {
    return Flux.just("John", "Jane", "Jack");
}

数据订阅

    result.subscribe(s -> log.info("get2 result: {}", s));

just 是立即包裹数据在异步响应中,类似传统mvc的响应体 ,线程会阻塞在这里等待包装结果

如果处理结果比较耗时也可以采用异步包装

// 阻塞5秒钟 模拟任务耗时
private String createStr() {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
    }
    return "some string";
}

// 普通的SpringMVC方法
@GetMapping("/1")
private String get1() {
    log.info("get1 start");
    String result = createStr();
    log.info("get1 end.");
    return result;
}

// WebFlux(返回的是Mono)
@GetMapping("/2")
private Mono<String> get2() {
    log.info("get2 start");
    Mono<String> result = Mono.fromSupplier(() -> createStr());
    log.info("get2 end.");
    return result;
}

代码到这里,很多时候,会把nio和多线程搞混了,这里肯定会想,线程执行这个方法,而此时不会阻塞等待结果执行完成,所以会认为当这个方法处理时间过长,返回响应的结果就为null
这样其实是很并发情况理解混乱了

由于nio的特性当代码块执行到这个createStr()时候,并不会阻塞,而是继续执行下面的代码,如果 createStr() 方法耗时很长,它的执行会被延迟到消费者(客户端)订阅 Mono 时。因此在 get2() 方法返回时,result 变量并不会导致返回 null。实际上是返回了一个包装了 createStr() 的 Mono 对象,而不是 createStr() 的结果,
Mono.fromSupplier(() -> createStr()) 会创建一个 Mono 实例,并将 createStr() 方法包装成一个供应者(supplier)。
此时,createStr() 不会被立即执行,只有在订阅 Mono 时,它才会执行。而在webflux构建的服务器中,用户的访问请求就类似一个隐式的订阅,mono包裹这个行为给客户端,客户端获取到这个行为,然后等当有客户端请求来订阅它时,createStr() 方法才会被调用。
如果 createStr() 在调用时耗时过长,客户端会在等待结果,但此时不会返回 null。相反,Spring WebFlux 会保持连接,直到 createStr() 完成,并通过 Mono 返回结果。

并且支持传统的mvc方式注册Controller·路由

@RestController
@RequestMapping("/api")
public class BasicController {
    @GetMapping("/names")
    public Flux<String> queryNmaes() {
        return Flux.just("Hello", "World", "张三", "李四");
    }

    @GetMapping("/users")
    public Mono<R<User>> queryUser() {
        User user = new User();
        user.setName("测试对象");
        user.setAge(666);
        return Mono.just(R.<User>builder().t(user)
                .msg("success")
                .build());
    }
}

也可以向gin和flask等web框架链式注册路由

    @Bean
    public RouterFunction<ServerResponse> routes(WebHandler handler) {
        return route()
                .GET("/hello", handler::hello)
                .build();
    }

5.2 Reactive Programming API

还有一个和mvc的不同就是流式处理,相当于mvc同步模型的stream流采用, Spring WebFlux 通过 Reactor 框架实现响应式编程。常用的操作符有 map, flatMap, filter, subscribe 等。

public Mono<String> getGreeting(String name) {
    return Mono.just(name)
    //数据格式转换
        .map(n -> "Hello, " + n)
        //一个数据转换为多个数据
        .flatMap(greeting -> Mono.just(greeting + " from WebFlux!"));
}

是的,你的理解是正确的。在 Spring WebFlux 中,Flux 提供了一种响应式编程的方式,类似于 Java 8 中的流式编程,但它是针对异步数据流的处理。

Flux.merge 示例解析

Flux.merge() 是一个非常有用的方法,它可以将多个 Flux 实例合并成一个单一的 Flux 实例。它会将所有传入的 Flux 中的数据流汇聚到一起,并保持它们的异步特性。

下面我们详细解析一下你的示例代码:

Flux.merge(
        Flux.just(1, 2, 3).delayElements(Duration.ofMillis(1)),
        Flux.just(4, 5, 6).delayElements(Duration.ofMillis(1))
).subscribe(System.out::println);
  1. Flux.just

Flux.just(1, 2, 3)Flux.just(4, 5, 6) 分别创建了两个 Flux 实例,分别包含了数字 1、2、3 和 4、5、6。

  1. delayElements

.delayElements(Duration.ofMillis(1)) 会让每个元素在发出前延迟 1 毫秒。这个方法的作用是模拟数据流的延迟,这在测试异步行为时特别有用。

  1. Flux.merge

Flux.merge(...) 将两个 Flux 实例合并成一个新的 Flux 实例。这个新的 Flux 将会发出来自所有输入 Flux 的元素,而不需要等待它们完成。

  1. subscribe

最后,.subscribe(System.out::println) 表示对合并后的 Flux 进行订阅。每当有元素发出时,System.out::println 会被调用,从而打印出这些元素。类似foreach

示例输出

由于每个 Flux 都有 1 毫秒的延迟,输出将会是:

1
4
2
5
3
6

输出的顺序是交替的,因为 Flux.merge() 会将来自两个源的元素混合在一起。

  1. 其他常用的 Flux API

除了 merge 方法,Flux 还提供了许多其他有用的 API,以下是一些常用的方法:

  • map: 对每个元素应用一个函数,并返回一个新的 Flux

    
    //把流的原素依次变大俩倍
       Flux<Integer> map = Flux.just(1, 2, 3)
                .map(i -> i * 2);
        map.subscribe(System.out::println);
    
  • filter: 只保留满足条件的元素。

    Flux.just(1, 2, 3, 4, 5)
        .filter(i -> i % 2 == 0) // 输出: 2, 4
        .subscribe(System.out::println);
    
  • flatMap: 将元素转换为多个元素的流,并将结果扁平化为一个 Flux

    Flux.just(1, 2, 3)
        .flatMap(i -> Flux.just(i, i * 10)) // 输出: 1, 10, 2, 20, 3, 30
        .subscribe(System.out::println);
    
  • reduce: 对所有元素应用一个归约操作,最终返回一个单一结果。

    Flux.just(1, 2, 3)
        .reduce(0, Integer::sum) // 输出: 6
        .subscribe(System.out::println);
    

collect 收集流中的原素

Flux<Integer> numbersFlux = Flux.just(1, 2, 3);
Mono<List<Integer>> listMono = numbersFlux.collectList(); // 收集成 List

  1. 结合 Java 8 Stream API 的对比
  • 流式编程:Java 的 Stream API 主要是同步的,它在操作数据时会阻塞当前线程,直到数据处理完成。
  • 响应式编程:Spring WebFlux 中的 Flux 是异步的,可以在等待 I/O 操作的同时处理其他任务。它通过非阻塞的方式来提高系统的响应能力和处理能力。
  1. 小结

Spring WebFlux 的 API 和 Java 8 的流式 API 在语法上看起来相似,但在处理数据的方式上有很大的不同

6. 响应式与传统同步编程的区别

在非阻塞 I/O 模型中,当一个线程发出 I/O 请求后,它并不会阻塞等待结果,而是立即释放,去处理其他任务。而对于这个 I/O 请求的处理,通常有以下的机制来完成:

  1. I/O 操作交给操作系统或 I/O 子系统

当线程发起 I/O 请求时,具体的 I/O 操作(如网络通信、文件读取等)不会在应用层的线程中完成,而是由操作系统内核I/O 子系统来负责。操作系统会负责执行实际的 I/O 操作,比如等待数据从网络中传输过来,或者等待磁盘完成读取。

  • 在非阻塞模型中,操作系统会通过底层的机制(如 epollselectkqueue 等)来监听和管理 I/O 事件。当某个 I/O 操作完成时,操作系统会发出通知,告知应用层的线程可以继续处理接下来的步骤。
  1. 回调机制处理结果

当 I/O 操作完成时,操作系统会通知应用层,应用层通常通过回调机制处理后续的逻辑。回调函数可以在一个事件循环中被执行,或者在线程池中重新分配给某个线程来执行。此时,真正执行这部分代码的是一个事件驱动线程或一个新分配的线程,而不是原来发出 I/O 请求的线程。

  • 回调机制是异步非阻塞 I/O 模型的核心。当 I/O 操作完成时,线程通过回调函数执行与该操作相关的业务逻辑。
  1. 谁执行回调代码?
  • 事件循环线程:如果是像 Netty 这样的框架,它内部会维护一个事件循环(event loop),该循环由少量线程组成,负责监听 I/O 操作的完成事件。当操作系统通知某个 I/O 操作完成后,事件循环会选择一个空闲的线程去执行该 I/O 操作的后续处理代码(即回调函数)。

  • 线程池中的线程:回调逻辑有时也可以交给一个线程池中的线程来处理。这种情况下,线程池中的某个空闲线程会执行回调函数,并继续完成 I/O 操作后的逻辑。

  1. 具体流程

举个例子来说明:

  1. 应用层线程发出一个网络请求,并调用 I/O 操作,例如从数据库读取数据。
  2. 该线程不等待读取结果,而是立即释放,继续处理其他任务。
  3. 操作系统负责真正的 I/O 操作(如等待数据库返回数据)。
  4. 一旦 I/O 操作完成,操作系统通过事件通知机制告知应用层 I/O 完成。
  5. 应用层的回调函数被触发,事件循环线程或线程池中的线程开始执行回调逻辑,处理 I/O 操作的结果。
6.1 线程池和并发处理

在 WebFlux 中,少量线程可以处理大量并发请求。通过异步 I/O 和事件驱动机制,线程池中的线程不会因为等待 I/O 操作而被阻塞,可以并行处理其他任务。

6.2 性能优化:减少线程上下文切换

传统的多线程阻塞 I/O 模型中,线程的上下文切换是性能瓶颈之一。WebFlux 通过少量线程高效处理 I/O 请求,减少了线程之间的切换和阻塞,从而提高了整体系统性能。
在传统的 Spring MVC 中,ThreadLocal 通常用于存储每个请求的用户上下文信息,因为每个请求都在独立的线程中处理,这样可以确保线程间不会相互干扰。然而,在 Spring WebFlux 中,由于采用了响应式编程模型和非阻塞 I/O,ThreadLocal 的使用并不适合,因为 WebFlux 使用的是反应式流,并且一个线程可能会处理多个请求。

WebFlux 中的上下文管理

在 WebFlux 中,由于不能使用threadlocal,所以使用 reactor.util.context.Context 来管理请求的上下文。这个上下文可以在异步执行的链中传递,确保每个请求在不同的上下文中运行,而不会互相影响。

使用 Context 存储用户上下文信息
  1. 创建过滤器:首先,你可以创建一个 WebFilter,在其中提取用户上下文信息并将其存储到 Context 中。

  2. 使用 deferContextual 读取上下文:在处理请求的过程中,可以通过 Mono.deferContextual 方法读取当前的上下文。

示例代码

以下是一个如何在 WebFlux 中使用 Context 存储和获取用户上下文信息的示例:

import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

@Component
public class UserContextFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        // 从请求头中获取用户ID
        String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
        
        // 将用户ID存储到上下文中
        return chain.filter(exchange)
                .contextWrite(Context.of("userId", userId)); // 将用户 ID 写入上下文
    }
}
在服务或控制器中使用上下文

在服务或控制器中,你可以使用 deferContextual 来访问上下文中的用户信息:

import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

@Service
public class UserService {

    public Mono<String> getUserData() {
        return Mono.deferContextual(context -> {
            // 从上下文中获取用户ID
            String userId = context.get("userId");
            // 根据用户 ID 进行处理
            return Mono.just("Data for user: " + userId);
        });
    }
}

在 Spring WebFlux 中,使用 Context 来存储和访问用户上下文信息,而不是使用 ThreadLocal。这种设计允许在异步和非阻塞的环境中有效地管理上下文,确保每个请求都在其独立的上下文中处理。这种方法避免了线程间的干扰,更加适合响应式编程模型。

7. Spring Security 与 WebFlux 的集成

Spring Security 提供了对 WebFlux 的支持,它也是非阻塞的,可以无缝集成到 WebFlux 中。

7.1 SecurityFilterChain 与 WebFlux

在 WebFlux 中,Spring Security 依赖 SecurityWebFilterChain 来处理请求。这个过滤器链是基于响应式流的,能够与异步 I/O 模型一起工作。

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
        .authorizeExchange()
        .pathMatchers("/admin/**").hasRole("ADMIN")
        .anyExchange().authenticated()
        .and().build();
}
7.2 上下文管理与异步安全处理

在 WebFlux 中,传统的 ThreadLocal 上下文管理方式不再适用,因为请求可能不会在同一个线程上完成。为了解决这个问题,WebFlux 提供了 Context 对象,可以在整个请求生命周期中传递安全上下文。

SecurityContext context = ReactiveSecurityContextHolder.getContext().block();

8. WebFlux 适用场景与性能分析

8.1 适用场景
  • 高并发、大量 I/O 操作:WebFlux 非常适合处理高并发请求,尤其是在 I/O 操作密集型的场景中(如与数据库、文件系统、远程 API 的交互)。
  • 流式数据处理:对于需要处理流式数据的应用,WebFlux 提供了强大的流处理能力(例如视频流、消息流等)。
    特别是netty搭配websocket 这样上下文切换资源优化很多
8.2 性能优势
  • 高效资源利用:通过事件驱动和非阻塞 I/O 模型,WebFlux 能够在少量线程的基础上处理大量并发请求,极大提升了资源的利用率。
  • **减少

阻塞时间**:传统的线程池模型容易受限于线程上下文切换和阻塞 I/O 操作,WebFlux 通过减少这些瓶颈提高了系统的响应速度。


原文地址:https://blog.csdn.net/qq_55272229/article/details/142872219

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!