Spring与响应式编程概述
这一篇是地基核心是干两件事先快速对齐 Spring 家底Bean、IoC、DI、模块划分、Spring Boot、Spring 6 新特性这部分对你来说大多是老朋友走马观花对齐术语即可然后正式打开响应式编程的大门讲清楚什么是响应式为什么要有它WebFlux 和你熟悉的 Spring MVC 差异在哪。第一部分·Spring 家底速览了解如果你有Java/Spring 开发背景这部分内容大概率是你日常工作里天天用的东西这里只做一次术语对齐和查漏补缺不逐条展开重点放在Spring 6 / Boot 3 有哪些新变化上——这是你可能还没来得及吃透的部分。Spring Framework 的核心卖点是依赖注入DI把对象之间谁依赖谁、怎么组装的工作从你手写的代码里剥离出来交给 Spring 容器统一管理你只需要写 POJOPlain Old Java Object业务逻辑。IoC控制反转是比 DI 更大的概念DI 是实现 IoC 的一种具体手段其他手段还有 Service Locator、工厂模式等。Spring 把这套能力拆成了大约 20 个模块归到几个大类Core ContainerCore/Beans/Context/Expression Language提供 DI 和 IoC 的地基、Data Access/IntegrationJDBC/ORM/OXM/JMS/事务、WebWeb/Web-Servlet/Web-PortletMVC 就在 Web-Servlet 里、AOP 与 Instrumentation、Test。Spring Boot 不是 Spring Framework 的分支而是建立在其之上的脚手架内嵌服务器不用打 WAR 包、starter 依赖自动配置、免 XML、自带健康检查和指标。一句话总结两者关系Spring Boot 负责怎么快速把 Spring 跑起来Spring Framework 负责跑起来之后有哪些能力可用。Spring Framework 62022 年 11 月和 Spring Boot 3 是同一波大版本几个硬性变化必须知道JDK 基线升到 17命名空间从javax.*全面迁移到jakarta.*比如javax.servlet变成jakarta.servletHibernate 也要用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 用 Mono0或1个元素和 Flux0到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 之外的另一条技术路线。它提供了两套编程风格一套是我熟悉的注解式RestControllerRequestMapping写法和 MVC 几乎一样另一套是函数式路由RouterFunction把路由声明和处理逻辑拆成显式的函数组合不再依赖注解反射去做映射。两者的差异不只是语法更在于返回值注解式里方法可以直接返回Mono/Flux函数式路由则要求处理函数返回MonoServerResponse真正返回给客户端的数据也必须包在响应式类型里而不是同步产出的普通对象。// 注解式写法和 Spring MVC 几乎一样但返回值换成响应式类型 Mono/Flux RestController public class ProductController { Autowired ProductService productService; GetMapping(/product) public FluxProduct productListing(){ return productService.getAllProducts(); // 返回 Flux数据异步陆续推送 } } // 函数式路由写法了解即可 Bean public RouterFunctionServerResponse products(ProductService productService) { return route() .GET(/product, request - ServerResponse.ok().body(productService.getAllProducts(), Product.class)) .build(); // route() 定义路由规则GET 绑定路径和处理函数两者组合成一条链 // 没有反射扫描注解的过程路由关系在代码里显式可见 // body() 接收的是 Publisher这里是 Flux底层负责把流式数据序列化后陆续写回响应体 }Spring WebFlux 的 FluxT 返回值相当于 CompletableFutureT 的响应式版本——方法不等结果就返回了结果准备好后框架自动推给客户端。区别在于 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 方法返回 MonoString 或 FluxT方法体只是声明了一条响应式流水线真正的执行在订阅时才发生。这个差异看似只是返回类型不同背后是两种完全不同的线程使用哲学。// Spring MVC —— 同步阻塞线程等待方法体执行完毕 GetMapping(/greeting) public String greeting() { return Hello, Spring MVC!; } // Spring WebFlux —— 异步非阻塞方法立即返回 Mono执行被延迟到订阅时 GetMapping(/greeting) public MonoString greeting() { return Mono.just(Hello, Spring WebFlux!); }安全机制上 WebFlux 复用 Spring Security用WebFilter拦截请求做鉴权思路和 MVC 的 Filter/Interceptor 链是同一个思路的响应式版本。MVC 和 WebFlux 之间还有一个我需要特别提醒自己的边界两者写出来的注解代码可以长得几乎一样但这只是语法层面的相似背后是完全独立的两套请求处理链路各自有自己的HandlerMapping、参数解析和返回值处理机制并不共享同一套底层实现。最稳妥的落地方式是按微服务粒度分别选型——比如一个服务用 MVCTomcat另一个用 WebFluxNetty团队按具体服务的场景各自决定。但如果考虑在同一个微服务里同时引入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 请求体的读和响应体的写这两个动作提供了非阻塞 APIReadListener/WriteListener但 Servlet 规范里其他所有环节——Filter 链的调用、Servlet 生命周期方法本身、getParameter()这类方法——依然是同步阻塞的。也就是说 Servlet 3.1 不是一个全异步的规范它只是给IO这一小块开了个非阻塞的口子。Spring 团队为了让 WebFlux 能跑在这种容器上专门写了一个适配层叫ServletHttpHandlerAdapter把 WebFlux 内部全异步的HttpHandler协议转译成 Servlet 3.1 那套只有IO非阻塞、其他还是同步的半吊子模型来跑。如果你是全新项目并且目标就是压榨非阻塞栈的极限吞吐Netty 才是发挥 WebFlux 真正威力的组合。