# 差别 Spring WebFlux 和传统的 Spring MVC (使用@Controller 和@RequestMapping 等注解定义的控制器)之间主要的区别在于它们对于处理请求的方式不同,这主要体现在响应式编程范式和阻塞式编程范式的差异上。下面我将通过几个方面来进行通俗说明。 ## 编程模型 **Spring MVC**: - **阻塞式 I/O 操作**:每个 HTTP 请求通常在一个单独的线程中被处理,如果请求处理涉及到长时间的 I/O 操作(如数据库查询),那么该线程会在操作完成之前处于阻塞状态。 - **注解驱动**:使用 `@Controller` 和 `@RequestMapping` 之类的注解来定义路由和处理方法,这些注解通常与同步操作一起使用。 **Spring WebFlux**: - **非阻塞式 I/O 操作**:使用响应式编程模型,即使操作是长时间运行的,也不会阻塞线程。而是在操作可以继续进行时,以回调的方式恢复执行。 - **函数式端点定义**:虽然 WebFlux 也支持与 Spring MVC 相同的注解方式,但它引入了一个新的函数式编程模型,可以使用路由器函数(RouterFunction)和处理器函数(HandlerFunction)来声明路由和处理逻辑。 ## 底层运行库 - **Spring MVC**:通常运行在阻塞式的 Web 服务器上,如 Tomcat。 - **Spring WebFlux**:可以运行在支持非阻塞运行环境的服务器上,如 Netty、Undertow。 ## 性能和资源利用 - **Spring MVC**:在高负载时,每个请求占用一个线程,可能会导致线程资源耗尽。 - **Spring WebFlux**:响应式编程使得可以用较少的线程来处理大量并发请求,从而提高了线程的利用率。 ## 应用场景 - **Spring MVC**:适用于传统的、请求 - 响应式的 Web 应用程序,特别是当你有很多的同步操作时。 - **Spring WebFlux**:适用于需要处理大量并发连接,以及要求低延迟 & 高吞吐量的实时性应用,例如实时数据处理、在线游戏后端等。 ## 影响 - **开发风格**:WebFlux 的函数式风格可能需要开发者学习新的编程范式,而 Spring MVC 的注解风格更符合大多数 Java 开发者的习惯。 - **可维护性**:WebFlux 的函数式风格可以带来更好的可测试性和可维护性,但这取决于开发者对响应式编程的熟悉程度。 - **响应性**:WebFlux 更适合做到背压和流式数据处理,可以更好地支持响应式系统的需求。 总结来说,Spring WebFlux 和 Spring MVC 提供了不同的编程模型来适应不同的应用场景。WebFlux 响应式编程模型适合处理大量并发请求和实时交互,而传统的 Spring MVC 更适合标准的、阻塞式的 Web 应用开发。选择使用哪个,取决于你的具体需求和开发团队的技术偏好。 # 非阻塞式 I/O 操作 非阻塞式 I/O 操作:使用响应式编程模型,即使操作是长时间运行的,也不会阻塞线程。而是在操作可以继续进行时,以回调的方式恢复执行。 这个特点。 下面是一个简单的 Spring WebFlux 的示例,展示了如何创建一个非阻塞的 RESTful 接口,该接口模拟了一个长时间运行的操作,比如查询数据库,但在这个示例中我们将使用 `Mono.delay` 来模拟这个耗时操作。 请注意,此代码需要在 Spring WebFlux 项目中运行,且 Spring Boot 版本应支持 WebFlux。 首先,我们需要一个模型,这里我们使用一个非常简单的 `Greeting` 类: ```java public class Greeting { private String message; public Greeting(String message) { this.message = message; } // getters and setters public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } ``` 接着,我们创建一个处理器(handler)来处理 HTTP 请求: ```java @Component public class GreetingHandler { public Mono hello(ServerRequest request) { // Mono.delay(Duration.ofSeconds(3)) 模拟一个长时间运行的操作 Mono greetingMono = Mono.delay(Duration.ofSeconds(3)) .map(l -> new Greeting("Hello, Spring WebFlux!")); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(greetingMono, Greeting.class); } } ``` 现在,我们需要定义路由来关联 URL 路径和处理器: ```java @Configuration public class GreetingRouter { @Bean public RouterFunction route(GreetingHandler greetingHandler) { return RouterFunctions .route(RequestPredicates.GET("/hello"), greetingHandler::hello); } } ``` 最后,这是 `SpringWebFluxDemoApplication` 的主类,用以启动 Spring Boot 应用: ```java @SpringBootApplication public class SpringWebFluxDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringWebFluxDemoApplication.class, args); } } ``` 要测试非阻塞行为,请在浏览器或使用 curl 访问 `http://localhost:8080/hello`。 在我们的 `GreetingHandler` 中,`Mono.delay(Duration.ofSeconds(3))` 模拟了一个长时间运行的 I/O 操作,这个操作在 3 秒后完成。这里的关键是,`Mono.delay` 创建了一个非阻塞的延迟操作,这意味着在这 3 秒内,执行这个操作的线程不会被阻塞,它可以去做其他的事情。 要测试非阻塞行为,请在浏览器或使用 curl 访问 `http://localhost:8080/hello`。即便我们在 `GreetingHandler` 中模拟了一个 3 秒的延迟,你会发现服务仍然可以接收和处理其他的请求。如果你并行地从多个终端或浏览器窗口发送请求,你会看到它们几乎是同时响应的,而不是一个接一个地阻塞等待。 为了完整起见,以下是使用 curl 命令行测试的方式: ```shell curl http://localhost:8080/hello ``` 如果你从多个终端窗口同时发出多个请求,你应该会发现响应时间几乎相同,这说明服务器没有因为模拟的延迟操作而阻塞其他请求。 这个简单的 demo 展示了 WebFlux 如何处理长时间运行的操作。在实际的生产环境中,类似的非阻塞操作可能包括数据库查询、远程服务调用等,这些操作可以利用响应式编程模型完成,而不会导致线程阻塞,从而提高了应用程序的性能和伸缩性。 # 真实场景 那真实的业务场景一般是怎么设计的?也是用 Mono.delay 吗?可以给我一个简答的真实场景的示例方案吗?比如一个数据查询接口,当收到请求后需要去大数据查询数据。查询大数据是比较耗时的。 当然可以。在许多微服务架构或分布式系统中,经常需要通过 HTTP 请求从其他服务或平台获取数据。如果你想在响应式编程模型中保持非阻塞式调用,你可以使用 Spring WebFlux 的 `WebClient`,这是一个响应式的 HTTP 客户端,用于执行 HTTP 请求并处理响应。 下面是如何使用 Spring WebFlux 的 `WebClient` 从另一个服务获取数据的示例: ```java @Service public class BigDataService { private final WebClient webClient; public BigDataService(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder.baseUrl("http://bigdata-service").build(); } public Mono queryBigData(String queryParam) { return webClient.get() .uri(uriBuilder -> uriBuilder.path("/query") .queryParam("param", queryParam) .build()) .retrieve() .bodyToMono(BigDataResponse.class); } } ``` 在这个服务中,我们创建了一个 `WebClient` 实例,它被配置为指向大数据平台的服务。`queryBigData` 方法通过 HTTP GET 请求发送查询参数,非阻塞地从大数据平台请求数据。返回的对象 `Mono` 是一个响应式的单值流,它将在异步操作完成时发出一个 `BigDataResponse` 对象。 然后你可以在控制层调用这个服务: ```java @RestController @RequestMapping("/data") public class DataController { private final BigDataService bigDataService; @Autowired public DataController(BigDataService bigDataService) { this.bigDataService = bigDataService; } @GetMapping("/query") public Mono> queryData(@RequestParam String param) { return bigDataService.queryBigData(param) .map(response -> ResponseEntity.ok(response)) .defaultIfEmpty(ResponseEntity.notFound().build()); } } ``` 这里,`DataController` 调用 `BigDataService` 的 `queryBigData` 方法,并将返回的 `Mono` 直接作为响应返回。Spring 框架会处理这个 `Mono`,当大数据平台返回结果时,将其序列化并发送到客户端。 使用 `WebClient`,Spring WebFlux 保证了整个 HTTP 调用过程是非阻塞的,即使是在等待远程大数据平台的响应时,也不会占用线程资源,这使得应用能够高效处理其他并发请求。