人人都会AI编程

第 25 章 响应式编程 Spring WebFlux

更新时间:2026-07-11

25.1 响应式编程的背景与核心理念

在传统Servlet模型中,每个请求都会分配一个线程来处理。如果业务中存在大量的阻塞I/O操作(数据库查询、远程调用、消息队列交互等),线程会被挂起等待,直到操作完成。当并发请求数上升时,线程池会被迅速耗尽,系统吞吐量急剧下降。虽然可以通过增大线程池、引入异步Servlet等方式缓解,但底层仍是“线程-per-request”的模式,资源利用率并不高。

响应式编程(Reactive Programming) 从根本上改变了这一模型。它的核心思想是:以非阻塞的方式处理数据流,用少量线程处理大量并发请求,通过异步事件驱动机制实现高吞吐和更好的弹性。在Java生态中,响应式流规范(Reactive Streams)定义了背压(backpressure)等关键机制,而Spring WebFlux正是基于这一规范构建的非阻塞Web框架。

WebFlux并不是要淘汰Spring MVC,而是提供一个补充选项。对于CPU密集型、纯粹依赖同步逻辑的应用,Spring MVC依然合适;但对于需要处理大量并发连接且多数时间在等待外部服务的场景(网关、消息推送、实时数据流等),WebFlux的优势会非常突出。

25.2 Project Reactor:响应式编程的基石

Spring WebFlux默认使用 Project Reactor 作为响应式库,它提供了 MonoFlux 两个核心类型。

  • Mono<T>:表示 0 或 1 个元素的异步序列,可以类比为“一个未来会返回的T或者空结果”。常用于HTTP请求返回单个对象。
  • Flux<T>:表示 0 到 N 个元素的异步序列,类似一个潜在的无限流。常用于返回列表、实时事件流等。

Reactor内置了丰富的操作符(map、flatMap、filter、zip、merge等),可以像函数式编程一样组合出复杂的异步处理管道,同时自动处理线程调度与背压。

下面是一个简单的Reactor示例,模拟从数据库查询用户然后转换为DTO的过程:

public Mono<UserDTO> getUserById(Long id) {
    return userRepository.findById(id)          // 返回 Mono<User>
            .map(user -> new UserDTO(user.getId(), user.getName()))  // 同步转换
            .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.NOT_FOUND)));
}

findById 返回一个耗时操作时(例如远程调用),线程不会阻塞等待结果,而是立即释放去处理其他任务,等到数据就绪后由Reactor的调度器触发后续操作。这样,少数几个线程就能支撑起数千个并发请求。

25.3 WebFlux 的两种编程模型

Spring WebFlux提供了两种开发风格:注解式模型函数式模型。两者都运行在相同的非阻塞容器上(Netty、Undertow、Jetty、Tomcat等),共享同样的响应式基础设施。实际项目可以根据团队习惯混合使用。

25.3.1 注解式模型(Annotated Controllers)

如果你已经熟悉Spring MVC的控制器写法,使用WebFlux的注解式模型几乎零成本切换。只需将返回值改为 MonoFlux 类型即可。

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public Mono<UserDTO> getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Mono<UserDTO> createUser(@Valid @RequestBody Mono<UserCreateRequest> requestMono) {
        return requestMono.flatMap(userService::createUser);
    }

    @GetMapping
    public Flux<UserDTO> listUsers() {
        return userService.findAllUsers();
    }
}

看上去和MVC几乎一样,区别在于返回值是响应式类型,并且方法参数可以接收 MonoFlux(如 @RequestBody Mono<UserCreateRequest>),Spring会自动处理复杂的响应式序列化。

校验也可以无缝工作,@Valid 配合 Mono 参数依然生效,校验失败会抛出 WebExchangeBindException

25.3.2 函数式模型(Functional Endpoints)

函数式模型更适合需要完全控制路由、请求处理流程的场景,也便于以更函数式的风格构造轻量级服务。它不需要 @Controller 等注解,而是通过 RouterFunctionHandlerFunction 定义路由和处理逻辑。

定义Handler

@Component
public class UserHandler {

    private final UserService userService;

    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    public Mono<ServerResponse> getUser(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return userService.getUserById(id)
                .flatMap(user -> ServerResponse.ok().bodyValue(user))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createUser(ServerRequest request) {
        return request.bodyToMono(UserCreateRequest.class)
                .flatMap(userService::createUser)
                .flatMap(user -> ServerResponse.created(URI.create("/users/" + user.getId()))
                        .bodyValue(user));
    }

    public Mono<ServerResponse> listUsers(ServerRequest request) {
        Flux<UserDTO> users = userService.findAllUsers();
        return ServerResponse.ok().body(users, UserDTO.class);
    }
}

定义Router

@Configuration
public class RouterConfig {

    @Bean
    public RouterFunction<ServerResponse> userRouter(UserHandler handler) {
        return RouterFunctions
                .route(RequestPredicates.GET("/users/{id}"), handler::getUser)
                .andRoute(RequestPredicates.POST("/users"), handler::createUser)
                .andRoute(RequestPredicates.GET("/users"), handler::listUsers);
    }
}

函数式模型中,ServerRequest 封装了请求信息,ServerResponse 用于构建响应。这种方式不再依赖任何注解,所有逻辑显式、可追踪。Router可以组合、嵌套、添加过滤器,非常适合微服务中的轻量边界服务。

25.4 过滤器与跨切面逻辑

WebFlux提供了两种过滤器机制,分别针对注解式模型和函数式模型。

1. WebFilter(全局过滤器)

实现 WebFilter 接口,可以拦截所有进入的请求,适用于日志、安全校验、CORS处理等:

@Component
public class LoggingFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        System.out.println("Request: " + exchange.getRequest().getPath());
        return chain.filter(exchange)
                .doOnSuccess(aVoid -> System.out.println("Response sent"));
    }
}

2. RouterFunction 过滤

在函数式模型的路由中,可以通过 RouterFunctions.route()filter 方法添加过滤器,或者使用 before/after 模式插入处理逻辑:

RouterFunctions.route()
    .GET("/users", handler::listUsers)
    .before(request -> {
        // 前置逻辑,如鉴权
        return request;
    })
    .after((request, response) -> {
        // 后置逻辑,如添加公共头
        return response;
    })

对于认证授权,Spring Security的响应式模块同样提供了 @EnableWebFluxSecuritySecurityWebFilterChain 的支持,用法与普通Security类似,只是返回类型使用了 Mono 类型。

25.5 异常处理

在注解式模型中,可以使用与MVC相同的 @ControllerAdvice + @ExceptionHandler

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Mono<ErrorResponse> handleNotFound(UserNotFoundException ex) {
        return Mono.just(new ErrorResponse("USER_NOT_FOUND", ex.getMessage()));
    }
}

在函数式模型中,则需要在Handler内部显式捕获异常并构建 ServerResponse,或者利用 onErrorResume 等Reactor操作符进行降级处理。

25.6 应用场景与选型建议

WebFlux并非银弹,它的优势在I/O密集型、流处理场景中体现得最为明显:

  • API网关:需要路由转发、聚合多个后端服务响应,并发量大且多数时间在等待。
  • 实时消息推送、SSE(Server-Sent Events):需要维持大量长连接,服务器需主动推送数据流。
  • 高并发的数据服务:如电商商品页聚合,需要同时调用库存、价格、评价等服务并合并结果。
  • 文件上传/下载代理:流式处理文件内容,避免将整个文件加载到内存。

如果你的应用主要进行CPU密集计算,或者团队对响应式模型(链式调用、调度、调试)不熟悉,强上WebFlux可能带来额外的开发复杂度。Spring MVC依旧稳定可靠,两者甚至可以共存于同一个Spring Boot应用中(通过不同端口配置),按模块逐步迁移。

25.7 实践要点与注意事项

  • 全链路非阻塞:WebFlux的效率建立在全栈非阻塞的基础上,如果UserService内部使用了 block() 方法或者调用了阻塞的JDBC驱动,响应式带来的优势会大打折扣。应配合响应式数据库驱动(如 R2DBC、MongoDB Reactive、Redis Reactive)使用。
  • 背压控制:处理流数据时需要注意消费者处理速度,避免内存溢出。Reactor内置了各种背压策略(buffer、drop、latest等),应根据场景合理选择。
  • 调试的挑战:响应式编程的调用栈比命令式复杂,堆栈信息较长且难以定位。可以使用 Hooks.onOperatorDebug() 开启调试模式(性能损耗较大,仅开发环境),或使用 Reactor 提供的 checkpoint() 操作符添加标记点。
  • Servlet容器的选择:虽然WebFlux可以运行在Tomcat等传统容器上,但性能最优的搭配是Netty,它天生异步非阻塞。在Spring Boot中引入 spring-boot-starter-webflux 时,默认就会使用Netty服务器。
  • 与MVC的区别总结
  • MVC基于Servlet API,每个请求绑定一个线程;WebFlux基于Reactive Streams,线程不阻塞等待。
  • MVC通常配合关系型数据库的JDBC(阻塞),WebFlux需要配合非阻塞数据访问层。
  • MVC使用 @Controller;WebFlux支持注解式和函数式两种路由定义。
  • 两者可以共存于同一个Boot应用中,但WebFlux的自动配置会取代部分MVC组件。

通过本章的学习,你应该已经掌握了WebFlux的核心概念和基础用法。在构建高吞吐、低延迟的现代Web应用时,响应式编程提供了一种强大的范式,而Spring WebFlux正是通向这一范式的成熟路径。