You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring @LoadBalanced RestTemplate拦截器重试时无法解析服务名如何解决?

问题原因

你的问题核心在于两点:

  1. 拦截器覆盖导致负载均衡逻辑失效:使用setInterceptors()会直接替换RestTemplate的默认拦截器列表,包括Spring Cloud自动为@LoadBalanced实例添加的LoadBalancerInterceptor。虽然第一次请求可能因版本兼容意外触发负载均衡,但重试时缺失该拦截器,导致服务名无法被解析。
  2. 拦截器执行顺序错误:即使负载均衡拦截器存在,若自定义拦截器在其之后执行,重试时调用execution.execute()会直接进入实际请求环节,跳过负载均衡解析步骤。

解决方案

1. 保留默认拦截器,追加自定义拦截器

不要用setInterceptors()替换默认列表,改用getInterceptors().add()添加自定义拦截器,确保负载均衡拦截器保留在执行链中:

@Bean
@LoadBalanced
public RestTemplate restTemplate() {
  var timeout = 15000;
  var clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
  clientHttpRequestFactory.setConnectTimeout(timeout);
  clientHttpRequestFactory.setConnectionRequestTimeout(timeout);
  RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
  // 追加自定义拦截器,而非替换默认拦截器
  restTemplate.getInterceptors().add(bearerTokenInterceptor);
  return restTemplate;
}

2. 强制调整拦截器执行顺序

如果追加后仍存在顺序问题,可以手动指定拦截器顺序,确保自定义拦截器在负载均衡拦截器之前执行:

@Bean
@LoadBalanced
public RestTemplate restTemplate(LoadBalancerInterceptor loadBalancerInterceptor) {
  var timeout = 15000;
  var clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
  clientHttpRequestFactory.setConnectTimeout(timeout);
  clientHttpRequestFactory.setConnectionRequestTimeout(timeout);
  RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
  
  // 先添加自定义拦截器,再添加负载均衡拦截器
  List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
  interceptors.add(bearerTokenInterceptor);
  interceptors.add(loadBalancerInterceptor);
  restTemplate.setInterceptors(interceptors);
  
  return restTemplate;
}

3. 重试时保留原始服务名URI

第一次请求后,负载均衡拦截器会将服务名URI替换为具体IP地址,因此重试时需要强制返回原始服务名URI,确保负载均衡逻辑能再次触发:

@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
  log.info("request get uri: {}", request.getURI());
  // 保存原始服务名形式的URI
  URI originalServiceUri = request.getURI();
  ClientHttpResponse response = execution.execute(request, body);

  if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) {
    accessToken = keycloakService.fetchAccessToken();

    HttpRequest retryRequest = new HttpRequestWrapper(request) {
      @Override
      public HttpHeaders getHeaders() {
        HttpHeaders headers = new HttpHeaders();
        headers.putAll(super.getHeaders());
        headers.setBearerAuth(accessToken);
        return headers;
      }

      @Override
      public URI getURI() {
        // 强制返回原始服务名URI,让负载均衡拦截器重新解析
        return originalServiceUri;
      }
    };
    // 关闭原响应资源,避免连接泄漏
    response.close();
    response = execution.execute(retryRequest, body);
  }
  return response;
}

额外注意事项

  • 重试前必须调用response.close()关闭原响应的输入流,防止HTTP连接池泄漏。
  • 确保keycloakService.fetchAccessToken()方法是线程安全的,避免多线程环境下令牌获取异常。

内容的提问来源于stack exchange,提问作者Minh Long Vu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:41:08