这一篇是地基,核心是干两件事:先快速对齐 Spring 家底(Bean、IoC、DI、模块划分、Spring Boot、Spring 6 新特性),这部分对你来说大多是"老朋友",走马观花对齐术语即可;然后正式打开响应式编程的大门,讲清楚"什么是响应式""为什么要有它""WebFlux 和你熟悉的 Spring MVC 差异在哪"。
第一部分·Spring 家底速览(了解)
如果你有Java/Spring 开发背景,这部分内容大概率是你日常工作里天天用的东西,这里只做一次术语对齐和查漏补缺,不逐条展开,重点放在"Spring 6 / Boot 3 有哪些新变化"上——这是你可能还没来得及吃透的部分。
Spring Framework 的核心卖点是依赖注入(DI):把对象之间"谁依赖谁、怎么组装"的工作从你手写的代码里剥离出来,交给 Spring 容器统一管理,你只需要写 POJO(Plain Old Java Object)业务逻辑。IoC(控制反转)是比 DI 更大的概念,DI 是实现 IoC 的一种具体手段(其他手段还有 Service Locator、工厂模式等)。Spring 把这套能力拆成了大约 20 个模块,归到几个大类:Core Container(Core/Beans/Context/Expression Language,提供 DI 和 IoC 的地基)、Data Access/Integration(JDBC/ORM/OXM/JMS/事务)、Web(Web/Web-Servlet/Web-Portlet,MVC 就在 Web-Servlet 里)、AOP 与 Instrumentation、Test。
Spring Boot 不是 Spring Framework 的分支,而是建立在其之上的"脚手架":内嵌服务器(不用打 WAR 包)、starter 依赖自动配置、免 XML、自带健康检查和指标。一句话总结两者关系:Spring Boot 负责"怎么快速把 Spring 跑起来",Spring Framework 负责"跑起来之后有哪些能力可用"。
Spring Framework 6(2022 年 11 月)和 Spring Boot 3 是同一波大版本,几个硬性变化必须知道:JDK 基线升到 17;命名空间从javax.*全面迁移到jakarta.*(比如javax.servlet变成jakarta.servlet,Hibernate 也要用jakarta.persistence);引入 AOT(构建期预处理 Bean 定义)配合 GraalVM 原生镜像,目标是加快启动、降低内存;HttpMethod从枚举变成了类;Spring MVC/WebFlux 的 Controller 识别规则收紧,必须显式标注@Controller或@RestController,仅有@RequestMapping不再算数。如果你的项目还在用旧版本准备升级,官方建议的路径是:先把 JDK 升到 17 并跑通全部单测,再升到 Spring 5/Boot 2 的最新小版本暴露出即将过时的用法,最后才升级到 Spring 6/Boot 3,逐个处理javax→jakarta的 import、Controller 注解补全等改动。
第二部分·响应式编程是什么、为什么要学它
Spring 家底对齐后,现在进入这一章真正的重点:响应式编程本身是什么、和你熟悉的同步阻塞式编程差在哪、什么时候该用它。
响应式编程(Reactive Programming)是一种以异步数据流(asynchronous data streams)为核心的声明式编程范式。我把它浓缩成一个公式来记:响应式编程 = Observable(可观察的数据流) + Change(数据流中发生的变化) + Propagation(变化向订阅者的传播)。换句话说,你不再是"调用一个方法,同步拿到一个值",而是"订阅一个流,数据在未来某个时刻陆续推送给你"。响应式编程建立在三个原则之上:异步(Asynchronous)、流(Streams)、变化传播(Propagation of change)。
这不是一个"知道语法就行"的知识点,而是一次编程模型的范式切换。你过去写 Java 后端,脑子里的默认模型是"同步阻塞 + 线程池扛并发":一个请求占一个线程,线程在等 DB/等下游接口时就阻塞在那里干等,并发上不去就多开线程。响应式模型的默认假设完全不同:线程不该被阻塞式地"占着等",一个数据库调用应该立刻返回(不阻塞调用线程),程序在后台把这个"未来会有的结果"挂起来,线程转头去干别的事,等结果真正来了再回调处理。这直接决定了你后面学习Reactor、Mono、Flux、背压(backpressure)等所有概念的效果,如果这里的心智模型没转过来,后面全是死记硬背。
第三部分·Java 生态里做响应式编程的2条路
Reactive Streams API是Java 9 引入的标准规范,定义了异步流处理 + 非阻塞背压的接口协议,注意它只是"规范/接口标准",是一套跨厂商的行业规范(由 Netflix、Pivotal/Spring、Lightbend 等在 2013 年左右联合发起),只定义了 Publisher/Subscriber/Subscription/Processor 四个接口和必须遵守的交互协议,本身不提供任何可运行的实现代码。Java 9 做的事情,是把这四个接口原封不动地内置进了java.util.concurrent.Flow,不是某个具体实现。
先记住定义:Publisher(发布者,向订阅者推送事件)、Subscriber(订阅者,接收并处理事件,有四个回调方法onNext/onSubscribe/onError/onComplete)、Subscription(订阅关系,描述一个 Publisher 和一个 Subscriber 之间的绑定,一个 Subscriber 同一时刻只能绑定一个 Publisher)、Processor(既是 Subscriber 又是 Publisher,代表流水线中间的处理环节)。
RxJava 和 Project Reactor 是两个平行的第三方实现,都去实现这套规范,RxJava 用 Observable 承载数据流,Reactor 用 Mono(0或1个元素)和 Flux(0到N个元素)承载数据流;Spring 生态选择了 Reactor 作为官方响应式底座,所以 Spring WebFlux 是构建在 Reactor 之上的 Web 框架,它和传统的 Spring MVC 是并列的两条 Web 层技术路线,都从属于更大的 Spring Framework 生态,而 Spring Boot 只是让 Spring Framework(不管是 MVC 那套还是 WebFlux 这套)更快跑起来的脚手架,不是并列关系而是"包裹"关系。
RxJava
由于RxJava不适合跨网络Spring Web应用开发,不做展开介绍。
Reactor 组件体系
第四部分·WebFlux 初探
两套编程风格
理解了响应式编程的动机之后,我开始正式接触 Spring 生态里落地这套思想的模块——Spring WebFlux,它是 Spring MVC 的"响应式替代品",是构建全异步、非阻塞 Web 应用的模块,基于事件循环(event-loop)执行机制,是 Spring MVC 之外的另一条技术路线。它提供了两套编程风格:一套是我熟悉的注解式(@RestController+@RequestMapping,写法和 MVC 几乎一样),另一套是函数式路由(RouterFunction),把路由声明和处理逻辑拆成显式的函数组合,不再依赖注解反射去做映射。两者的差异不只是语法,更在于返回值:注解式里方法可以直接返回Mono/Flux,函数式路由则要求处理函数返回Mono<ServerResponse>,真正返回给客户端的数据也必须包在响应式类型里,而不是同步产出的普通对象。
// 注解式写法(和 Spring MVC 几乎一样,但返回值换成响应式类型 Mono/Flux) @RestController public class ProductController { @Autowired ProductService productService; @GetMapping("/product") public Flux<Product> productListing(){ return productService.getAllProducts(); // 返回 Flux,数据异步陆续推送 } } // 函数式路由写法(了解即可) @Bean public RouterFunction<ServerResponse> products(ProductService productService) { return route() .GET("/product", request -> ServerResponse.ok().body(productService.getAllProducts(), Product.class)) .build(); // route() 定义路由规则,GET 绑定路径和处理函数,两者组合成一条链, // 没有反射扫描注解的过程,路由关系在代码里显式可见; // body() 接收的是 Publisher(这里是 Flux),底层负责把流式数据序列化后陆续写回响应体 }Spring WebFlux 的 Flux<T> 返回值相当于 CompletableFuture<T> 的响应式版本——方法不等结果就返回了,结果准备好后框架自动推给客户端。区别在于 CompletableFuture 是一次性的(一个 future 对应一个结果),而 Mono/Flux 还支持背压、取消、流式推送等响应式语义。
webFlux典型应用:
第五部分·WebFlux vs MVC选型
Spring MVC 和 Spring WebFlux 不是升级版关系、不是替换关系。它们是 Spring 生态中并列的两条 Web 层路线。Spring MVC 基于 Servlet API,采用 Thread-Per-Request 模型——每个请求占用一个线程,线程在等待 I/O(数据库查询、远程调用)时被阻塞挂起,直到 I/O 完成才释放。Spring WebFlux 基于 Reactive Streams 规范,采用 Event-Loop 模型——少量线程通过事件循环处理大量请求,I/O 等待期间线程不被阻塞,可以去处理其他请求。它可以看作是把这套非阻塞思想从网络 I/O 层搬到了整个应用层(包括数据库访问、下游 RPC 调用)。
两者的代码写法差异一眼可见。Spring MVC 的 Controller 方法直接返回 String 或 ResponseEntity,方法体同步执行完毕才返回;Spring WebFlux 的 Controller 方法返回 Mono<String> 或 Flux<T>,方法体只是声明了一条响应式流水线,真正的执行在订阅时才发生。这个差异看似只是返回类型不同,背后是两种完全不同的线程使用哲学。
// Spring MVC —— 同步阻塞,线程等待方法体执行完毕 @GetMapping("/greeting") public String greeting() { return "Hello, Spring MVC!"; } // Spring WebFlux —— 异步非阻塞,方法立即返回 Mono,执行被延迟到订阅时 @GetMapping("/greeting") public Mono<String> greeting() { return Mono.just("Hello, Spring WebFlux!"); }安全机制上 WebFlux 复用 Spring Security,用WebFilter拦截请求做鉴权,思路和 MVC 的 Filter/Interceptor 链是同一个思路的响应式版本。
MVC 和 WebFlux 之间还有一个我需要特别提醒自己的边界:两者写出来的注解代码可以长得几乎一样,但这只是语法层面的相似,背后是完全独立的两套请求处理链路(各自有自己的HandlerMapping、参数解析和返回值处理机制),并不共享同一套底层实现。最稳妥的落地方式是按微服务粒度分别选型——比如一个服务用 MVC+Tomcat,另一个用 WebFlux+Netty,团队按具体服务的场景各自决定。
但如果考虑在同一个微服务里同时引入spring-boot-starter-web和spring-boot-starter-webflux,有一个隐蔽的坑必须提前知道:即便在启动类里显式声明了WebApplicationType.REACTIVE,实际跑起来的底层容器仍然可能是 Tomcat 而不是预期的 Netty。原因是WebApplicationType只决定编程模型层面能不能用Mono/Flux,并不决定底层 HTTP 服务器选谁,真正拍板的是 Spring Boot 自动配置里的ReactiveWebServerFactoryConfiguration,它同时注册了 Netty、Tomcat、Jetty、Undertow 四个候选配置,谁的 Bean 先被处理到,谁就留下,跟启动类里怎么声明毫无关系。想在这种混用场景下确保用的是 Netty,唯一可靠的办法是自己显式声明一个ReactiveWebServerFactory类型的 Bean,把 Netty 的候选资格提前锁死,因为候选容器之间是互斥关系,容器里已经存在一个同类型 Bean 后,其余几个自动配置会直接跳过。判断一个 Spring Boot 应用底层实际用的是哪个服务器,最可靠的方式永远是去看启动日志里xxx started on port那一行,代码里声明的WebApplicationType只是个容易误导人的表面信号。
Spring Boot 用 WebFlux 时默认内嵌的服务器就是 Netty,但如果你显式换成 Tomcat,程序完全能正常启动、正常处理请求,不会抛异常,官方文档原话是"runs on such servers as Netty, and Servlet containers",把 Servlet 容器和 Netty 并列为官方支持的两类底座。容易被"能跑"这个表面结论掩盖的地方:Servlet 3.1 规范本身只对 HTTP 请求体的读和响应体的写这两个动作提供了非阻塞 API(ReadListener/WriteListener),但 Servlet 规范里其他所有环节——Filter 链的调用、Servlet 生命周期方法本身、getParameter()这类方法——依然是同步阻塞的。也就是说 Servlet 3.1 不是一个"全异步"的规范,它只是给IO这一小块开了个非阻塞的口子。Spring 团队为了让 WebFlux 能跑在这种容器上,专门写了一个适配层叫ServletHttpHandlerAdapter,把 WebFlux 内部全异步的HttpHandler协议,转译成 Servlet 3.1 那套"只有IO非阻塞、其他还是同步"的半吊子模型来跑。如果你是全新项目并且目标就是压榨非阻塞栈的极限吞吐,Netty 才是发挥 WebFlux 真正威力的组合。