Spring MVC中WebClient阻塞操作的影响及验证方法咨询
场景背景
我有一个基于Spring MVC的Spring Boot应用,同时引入了以下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
已知同时引入这两个依赖时,Spring Boot会自动配置Spring MVC而非WebFlux。
应用的REST控制器代码如下:
@RestController public class EmployeeRestController { @Autowired private EmployeeService service; @GetMapping("/api/employee/get/{id}") public Employee getEmployee(@PathVariable("id") int id) { return service.fetchEmployeeData(id); } }
服务层通过WebClient调用第三方API,代码如下:
@Service public class EmployeeService { private final WebClient webClient; public EmployeeService(WebClient.Builder webClientBuilder) { this.webClient = webClientBuilder.baseUrl("http://mydatasource.com/employees").build(); } public Employee fetchEmployeeData(int id) { Employee result = this.webClient .get() .url(id) .retrieve() .bodyToMono(Employee.class) .block(); // 其他业务逻辑处理 return result; } }
核心疑问
我知道响应式编程中不建议使用block()操作,但我的假设是:由于Spring MVC运行在阻塞栈上,遵循“一个请求对应一个线程”的模式,这里的block()只会阻塞当前请求分配的线程——这个理解是否正确?如果正确,如何简单验证这一点?
解答
1. 你的理解完全正确
在Spring MVC默认的Servlet容器(如Tomcat)中,每个请求会被分配到容器线程池中的一个专属线程。当调用block()时,只会阻塞当前处理该请求的MVC线程,不会影响线程池中的其他线程,也不会阻塞WebClient底层的IO线程(Reactor的NIO线程)。
WebClient的异步请求由Reactor的IO线程负责处理,MVC的请求线程会进入等待状态,直到block()获取到响应结果后才继续执行后续业务逻辑。期间该MVC线程被占用,但线程池中的其他线程仍能正常处理其他请求。
2. 两种简单验证方法
方法一:打印线程ID对比
在关键代码位置添加线程ID打印,直观验证线程归属:
- 在控制器方法开头添加:
System.out.println("MVC请求线程ID: " + Thread.currentThread().getId()); - 在
fetchEmployeeData方法中,block()前后分别添加:System.out.println("block前的线程ID: " + Thread.currentThread().getId()); Employee result = this.webClient.get().url(id).retrieve().bodyToMono(Employee.class).block(); System.out.println("block后的线程ID: " + Thread.currentThread().getId()); - 可选:给WebClient添加拦截器,打印IO线程ID:
this.webClient = webClientBuilder .baseUrl("http://mydatasource.com/employees") .filter((request, next) -> { System.out.println("WebClient IO线程ID: " + Thread.currentThread().getId()); return next.exchange(request); }) .build();
验证结果:MVC请求线程ID与block()前后的线程ID完全一致,而WebClient的IO线程ID明显不同。这直接证明block()阻塞的是处理当前请求的MVC线程。
方法二:高并发压测验证
使用JMeter、Postman Runner等工具批量发送请求,同时通过Spring Boot Actuator的/actuator/threads端点监控线程池状态:
- 若
block()仅阻塞当前请求线程,高并发下会看到容器线程池中的线程被逐个占用(处于阻塞状态),但只要线程池还有空闲线程,新请求就能被处理;当线程池耗尽后,新请求会进入等待队列,这完全符合Spring MVC的阻塞线程模型特征。 - 若出现整个应用无法处理请求的情况,才说明存在全局阻塞,但实际不会发生这种情况。
内容的提问来源于stack exchange,提问作者Remo

