同步模式下Spring WebClient拦截器block()报错的解决方案咨询
问题解决:WebClient拦截器中block()报错的解决方案
问题背景
我们项目用Spring WebClient调用外部API,不需要响应式方案,所以采用同步模式。该外部API需要先授权,再在请求的Authorization头中携带令牌。我们编写了拦截器自动补充令牌,但遇到错误:block()/blockfirst()/blocklast() are blocking, which is not supported in thread parallel-1
可行解决方案
方案1:改用响应式链式调用(推荐)
WebClient的拦截器ExchangeFilterFunction本身是响应式设计,返回Mono<ClientResponse>,所以别用block()阻塞获取令牌,而是用flatMap把令牌获取和原请求串联成响应式链条,避开线程阻塞限制。
修改后的拦截器代码:
private ExchangeFilterFunction authExchangeFilterFunction() { return (request, next) -> { // 建议把授权用的WebClient做成Bean,避免每次请求都重新构建 WebClient authWebClient = WebClient.builder() .baseUrl(properties.getBaseUrl()) .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) .filter(ExchangeFilterFunctions.basicAuthentication( properties.getAuth().getBasic().getUsername(), properties.getAuth().getBasic().getPassword() )) .filter(WebclientUtils.exchangeFilterLogRequest()) .filter(WebclientUtils.exchangeFilterLogResponse()) .build(); // 生成授权所需的表单数据(根据实际接口要求调整) MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); formData.add("grant_type", "client_credentials"); // 补充其他必要参数... // 响应式获取令牌,再继续处理原请求 return authWebClient.post() .uri("/oauth/token") .body(BodyInserters.fromFormData(formData)) .retrieve() .bodyToMono(String.class) .flatMap(token -> { // 给原请求添加Authorization头(注意格式,通常是Bearer + 令牌) ClientRequest authorizedRequest = ClientRequest.from(request) .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .build(); // 执行后续过滤器和请求 return next.exchange(authorizedRequest); }); }; }
方案2:用同步HTTP客户端获取令牌(适配纯同步场景)
如果坚持要用同步逻辑,别在拦截器里用WebClient的block(),改用RestTemplate(Spring官方同步HTTP客户端)来获取令牌——RestTemplate本身就是同步阻塞设计,不会运行在Reactor的线程池中,自然不会触发线程限制。
步骤1:定义用于授权的RestTemplate Bean
@Bean public RestTemplate authRestTemplate(ApiProperties properties) { RestTemplate restTemplate = new RestTemplate(); // 添加Basic认证拦截器 restTemplate.getInterceptors().add((req, body, execution) -> { String authStr = properties.getAuth().getBasic().getUsername() + ":" + properties.getAuth().getBasic().getPassword(); String authBase64 = Base64Utils.encodeToString(authStr.getBytes(StandardCharsets.UTF_8)); req.getHeaders().add(HttpHeaders.AUTHORIZATION, "Basic " + authBase64); return execution.execute(req, body); }); return restTemplate; }
步骤2:修改拦截器代码,注入RestTemplate获取令牌
private final RestTemplate authRestTemplate; private final ApiProperties properties; // 通过构造器注入依赖 public YourConfigClass(RestTemplate authRestTemplate, ApiProperties properties) { this.authRestTemplate = authRestTemplate; this.properties = properties; } private ExchangeFilterFunction authExchangeFilterFunction() { return (request, next) -> { // 生成授权表单数据 MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); formData.add("grant_type", "client_credentials"); // 补充其他参数... // 同步获取令牌 ResponseEntity<String> tokenResponse = authRestTemplate.postForEntity( properties.getBaseUrl() + "/oauth/token", formData, String.class ); String token = tokenResponse.getBody(); // 修改原请求并继续执行 ClientRequest authorizedRequest = ClientRequest.from(request) .header(HttpHeaders.AUTHORIZATION, "Bearer " + token) .build(); return next.exchange(authorizedRequest); }; }
额外优化建议
不管用哪种方案,都要缓存令牌(注意令牌的过期时间),避免每次请求都调用授权接口浪费资源。可以用Guava Cache或者Spring的@Cacheable注解实现缓存逻辑。
内容的提问来源于stack exchange,提问作者morohon
相关产品推荐
相关产品推荐

