Files
2026-03-01 01:43:46 +08:00

185 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 差别
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<ServerResponse> hello(ServerRequest request) {
// Mono.delay(Duration.ofSeconds(3)) 模拟一个长时间运行的操作
Mono<Greeting> 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<ServerResponse> 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<BigDataResponse> 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>` 是一个响应式的单值流,它将在异步操作完成时发出一个 `BigDataResponse` 对象。
然后你可以在控制层调用这个服务:
```java
@RestController
@RequestMapping("/data")
public class DataController {
private final BigDataService bigDataService;
@Autowired
public DataController(BigDataService bigDataService) {
this.bigDataService = bigDataService;
}
@GetMapping("/query")
public Mono<ResponseEntity<BigDataResponse>> 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 调用过程是非阻塞的,即使是在等待远程大数据平台的响应时,也不会占用线程资源,这使得应用能够高效处理其他并发请求。