9.1 KiB
差别
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 类:
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 请求:
@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 路径和处理器:
@Configuration
public class GreetingRouter {
@Bean
public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
return RouterFunctions
.route(RequestPredicates.GET("/hello"), greetingHandler::hello);
}
}
最后,这是 SpringWebFluxDemoApplication 的主类,用以启动 Spring Boot 应用:
@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 命令行测试的方式:
curl http://localhost:8080/hello
如果你从多个终端窗口同时发出多个请求,你应该会发现响应时间几乎相同,这说明服务器没有因为模拟的延迟操作而阻塞其他请求。
这个简单的 demo 展示了 WebFlux 如何处理长时间运行的操作。在实际的生产环境中,类似的非阻塞操作可能包括数据库查询、远程服务调用等,这些操作可以利用响应式编程模型完成,而不会导致线程阻塞,从而提高了应用程序的性能和伸缩性。
真实场景
那真实的业务场景一般是怎么设计的?也是用 Mono.delay 吗?可以给我一个简答的真实场景的示例方案吗?比如一个数据查询接口,当收到请求后需要去大数据查询数据。查询大数据是比较耗时的。
当然可以。在许多微服务架构或分布式系统中,经常需要通过 HTTP 请求从其他服务或平台获取数据。如果你想在响应式编程模型中保持非阻塞式调用,你可以使用 Spring WebFlux 的 WebClient,这是一个响应式的 HTTP 客户端,用于执行 HTTP 请求并处理响应。
下面是如何使用 Spring WebFlux 的 WebClient 从另一个服务获取数据的示例:
@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 对象。
然后你可以在控制层调用这个服务:
@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 调用过程是非阻塞的,即使是在等待远程大数据平台的响应时,也不会占用线程资源,这使得应用能够高效处理其他并发请求。