Spring @LoadBalanced RestTemplate拦截器重试时无法解析服务名如何解决?
问题原因
你的问题核心在于两点:
- 拦截器覆盖导致负载均衡逻辑失效:使用
setInterceptors()会直接替换RestTemplate的默认拦截器列表,包括Spring Cloud自动为@LoadBalanced实例添加的LoadBalancerInterceptor。虽然第一次请求可能因版本兼容意外触发负载均衡,但重试时缺失该拦截器,导致服务名无法被解析。 - 拦截器执行顺序错误:即使负载均衡拦截器存在,若自定义拦截器在其之后执行,重试时调用
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
相关产品推荐
相关产品推荐

