Spring Cloud中restTemplate是如何通过服务名主求到具体服务的?

最近的项目是基于spring cloud中,其中对于服务的调用,是通过restTemplate来发送http请求调用的,但请求地址为http://SERVICE-NAME/questpath,如下面这样:

@RestController
@RequestMapping(value = "hello")
public class HelloController {
    private String serviceName = "FRIEND-SERVICE";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "school")
    public String test1() {
        String result = restTemplate.getForObject("http://" + serviceName + "/school/get", String.class);
        return "this is requestResult:" + result;
    }

    @RequestMapping(value = "univers/{schoolId}")
    public String universList(@PathVariable(value = "schoolId") String schoolId) {
        String univsOfSchool = restTemplate.getForObject("http://" + serviceName + "/school/univs/" + schoolId, String.class);
        return univsOfSchool;
    }


}

其中的RestTemplate是这样初始化的:

@SpringBootApplication
@EnableEurekaClient
public class BizClientApplication {

    @Bean
    @LoadBalanced
    RestTemplate initRestTemplate() {
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(BizClientApplication.class, args);
    }
}

刚开始时见网上一些大牛们的博客里是这样的写的,于是我便照搬了过来能实现目标就可以。今天闲暇时,又想到了这个问题:在我印象中restTemplate是一个类似于httpClient的框架而已,为什么在spring cloud中不需要写具体的域名或IP+端口号就能实现请求呢?而不是这样的:http://192.168.1.102:8080/requestPath

然后报着研究的心态,将RestTemplate的@LoadBanlanced注解注释掉了后发现通过http://SERVICE-NAME/questpath的方式去请求别的服务又不行了,报了主机找不到的错误

于是乎决定通过Debug跟踪代码研究下SpingCloud下RestTemplate请求服务的原理究竟是怎样实现的.

从restTemplate开始

@RequestMapping(value = "school")
public String test1() {
    String result = restTemplate.getForObject("http://" + serviceName + "/school/get", String.class);
    return "this is requestResult:" + result;
}

restTemplate请求代码主线跟踪

一直跟进代码,直到doExcute方法处

/**
 * Execute the given method on the provided URI.
 * <p>The {@link ClientHttpRequest} is processed using the {@link RequestCallback};
 * the response with the {@link ResponseExtractor}.
 * @param url the fully-expanded URL to connect to
 * @param method the HTTP method to execute (GET, POST, etc.)
 * @param requestCallback object that prepares the request (can be {@code null})
 * @param responseExtractor object that extracts the return value from the response (can be {@code null})
 * @return an arbitrary object, as returned by the {@link ResponseExtractor}
 */
protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback,
      ResponseExtractor<T> responseExtractor) throws RestClientException {

   Assert.notNull(url, "'url' must not be null");
   Assert.notNull(method, "'method' must not be null");
   ClientHttpResponse response = null;
   try {
      ClientHttpRequest request = createRequest(url, method);
      if (requestCallback != null) {
         requestCallback.doWithRequest(request);
      }
      response = request.execute();
      handleResponse(url, method, response);
      if (responseExtractor != null) {
         return responseExtractor.extractData(response);
      }
      else {
         return null;
      }
   }
   catch (IOException ex) {
      String resource = url.toString();
      String query = url.getRawQuery();
      resource = (query != null ? resource.substring(0, resource.indexOf('?')) : resource);
      throw new ResourceAccessException("I/O error on " + method.name() +
            " request for \"" + resource + "\": " + ex.getMessage(), ex);
   }
   finally {
      if (response != null) {
         response.close();
      }
   }
}

由此看出请求具体方法的内容就在此行了: response = request.execute();

继续跟进request.excute()方法,发现如下关键代码:

@Override
protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
   InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
   return requestExecution.execute(this, bufferedOutput);
}

通过上面可以看出restTemplate在执行上面的 HTTP请求时是执行的requestExceution的excute方法。那么这里的InterceptingRequestExcution的具体内容是啥呢? go on.

private class InterceptingRequestExecution implements ClientHttpRequestExecution {

   private final Iterator<ClientHttpRequestInterceptor> iterator;

   public InterceptingRequestExecution() {
      this.iterator = interceptors.iterator();
   }

   @Override
   public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
      if (this.iterator.hasNext()) {
         ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
         return nextInterceptor.intercept(request, body, this);
      }
      else {
         ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
         for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
            List<String> values = entry.getValue();
            for (String value : values) {
               delegate.getHeaders().add(entry.getKey(), value);
            }
         }
         if (body.length > 0) {
            StreamUtils.copy(body, delegate.getBody());
         }
         return delegate.execute();
      }
   }
}

这里的InterceptingRequestExecution 类是一个内部类,位于主类InterceptingClientHttpRequest中。从InterceptingRequestExecution 这个类中可以看出这个类中唯一成员变量iterator是由其主类的interceptors获取的。那么主类中的iterceptors又是在哪里设置的呢? 这里先不管,待会再看。

class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest {

   private final ClientHttpRequestFactory requestFactory;

   private final List<ClientHttpRequestInterceptor> interceptors;

继续跟着主线的requestExecution.execute方法,debug中表示此方法最终调用到了nextInterceptor.intercept(request, body, this)方法上,此时的nextInterceptor为一个实现了ClientHttpRequestInterceptor接口名为LoadBanlancerInterceptor的类

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

   private LoadBalancerClient loadBalancer;
   private LoadBalancerRequestFactory requestFactory;

   public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
      this.loadBalancer = loadBalancer;
      this.requestFactory = requestFactory;
   }

   public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
      // for backwards compatibility
      this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
   }

   @Override
   public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
         final ClientHttpRequestExecution execution) throws IOException {
      final URI originalUri = request.getURI();
      String serviceName = originalUri.getHost();
      Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
      return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
   }
}

到这里在springCloud中restTemplate请求一个http的方法就可以明白了。在@LoadBalanced注解给RestTemplate开启的情况下最终去主要执行的HTTP请求的为LoadBalancerInterceptor 这个类下的loadBalancer的execute方法。

loadBalancer方法跟踪

继续Debug了下loadBalancer代码,发现此时的this.loadBalancer是一个实现了LoadBalancerClient接口名为RibbonLoadBalancerClient的类。(看到这个类名,疑惑感觉解开了一大半了)

this.loadBalancer.execute最终执行的方法为:RibbonLoadBalancerClient下的此方法:

@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
   ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
   Server server = getServer(loadBalancer);
   if (server == null) {
      throw new IllegalStateException("No instances available for " + serviceId);
   }
   RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
         serviceId), serverIntrospector(serviceId).getMetadata(server));

   return execute(serviceId, ribbonServer, request);
}

这个方法个人感觉是一个体现了spring cloud负载均衡的主要方法.

从代码里可以大致看出如下步骤:

1.先用serviceId参数通过getLoadBalancer获取到了一个loadBalancer

2.通过getServer方法获取出了这个serviceId的service信息

3.通过获取到的service信息去执行最终的http请求

execute(serviceId,ribbonServer,request)的实现方法为:

@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
   Server server = null;
   if(serviceInstance instanceof RibbonServer) {
      server = ((RibbonServer)serviceInstance).getServer();
   }
   if (server == null) {
      throw new IllegalStateException("No instances available for " + serviceId);
   }

   RibbonLoadBalancerContext context = this.clientFactory
         .getLoadBalancerContext(serviceId);
   RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);

   try {
      T returnVal = request.apply(serviceInstance);
      statsRecorder.recordStats(returnVal);
      return returnVal;
   }
   // catch IOException and rethrow so RestTemplate behaves correctly
   catch (IOException ex) {
      statsRecorder.recordStats(ex);
      throw ex;
   }
   catch (Exception ex) {
      statsRecorder.recordStats(ex);
      ReflectionUtils.rethrowRuntimeException(ex);
   }
   return null;
}

所以当代码执行到request.apply(serviceInstance)时已经获取到服务器具体信息了,所以一切豁然开朗了吧!


获取服务名的关键代码为LoadBalancerContext类中的代码:

public URI reconstructURIWithServer(Server server, URI original) {
    String host = server.getHost();
    int port = server .getPort();
    if (host.equals(original.getHost()) 
            && port == original.getPort()) {
        return original;
    }
    String scheme = original.getScheme();
    if (scheme == null) {
        scheme = deriveSchemeAndPortFromPartialUri(original).first();
    }

    try {
        StringBuilder sb = new StringBuilder();
        sb.append(scheme).append("://");
        if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
            sb.append(original.getRawUserInfo()).append("@");
        }
        sb.append(host);
        if (port >= 0) {
            sb.append(":").append(port);
        }
        sb.append(original.getRawPath());
        if (!Strings.isNullOrEmpty(original.getRawQuery())) {
            sb.append("?").append(original.getRawQuery());
        }
        if (!Strings.isNullOrEmpty(original.getRawFragment())) {
            sb.append("#").append(original.getRawFragment());
        }
        URI newURI = new URI(sb.toString());
        return newURI;            
    } catch (URISyntaxException e) {
        throw new RuntimeException(e);
    }
}
其他详细的还有待深入挖掘!

restTemplate中的LoadBalancerInterceptor是何时被设置到restTemplate中的?

restTemplate中的LoadBalancerInterceptor是何时被设置到restTemplate中的?这个我一开始也不知道,后来google了下,找到结果了.

详见地址:https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java

/**
 * Auto configuration for Ribbon (client side load balancing).
 *
 * @author Spencer Gibb
 * @author Dave Syer
 * @author Will Tran
 */
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

   @LoadBalanced
   @Autowired(required = false)
   private List<RestTemplate> restTemplates = Collections.emptyList();

   @Bean
   public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
         final List<RestTemplateCustomizer> customizers) {
      return new SmartInitializingSingleton() {
         @Override
         public void afterSingletonsInstantiated() {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
               for (RestTemplateCustomizer customizer : customizers) {
                  customizer.customize(restTemplate);
               }
            }
         }
      };
   }

   @Autowired(required = false)
   private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

   @Bean
   @ConditionalOnMissingBean
   public LoadBalancerRequestFactory loadBalancerRequestFactory(
         LoadBalancerClient loadBalancerClient) {
      return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
   }

   @Configuration
   @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
   static class LoadBalancerInterceptorConfig {
      @Bean
      public LoadBalancerInterceptor ribbonInterceptor(
            LoadBalancerClient loadBalancerClient,
            LoadBalancerRequestFactory requestFactory) {
         return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
      }

      @Bean
      @ConditionalOnMissingBean
      public RestTemplateCustomizer restTemplateCustomizer(
            final LoadBalancerInterceptor loadBalancerInterceptor) {
         return new RestTemplateCustomizer() {
            @Override
            public void customize(RestTemplate restTemplate) {
               List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                     restTemplate.getInterceptors());
               list.add(loadBalancerInterceptor);
               restTemplate.setInterceptors(list);
            }
         };
      }
   }

   @Configuration
   @ConditionalOnClass(RetryTemplate.class)
   static class RetryAutoConfiguration {
      @Bean
      public RetryTemplate retryTemplate() {
         RetryTemplate template =  new RetryTemplate();
         template.setThrowLastExceptionOnExhausted(true);
         return template;
      }

      @Bean
      @ConditionalOnMissingBean
      public LoadBalancedRetryPolicyFactory loadBalancedRetryPolicyFactory() {
         return new LoadBalancedRetryPolicyFactory.NeverRetryFactory();
      }

      @Bean
      public RetryLoadBalancerInterceptor ribbonInterceptor(
            LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
            LoadBalancedRetryPolicyFactory lbRetryPolicyFactory,
            LoadBalancerRequestFactory requestFactory) {
         return new RetryLoadBalancerInterceptor(loadBalancerClient, retryTemplate(), properties,
               lbRetryPolicyFactory, requestFactory);
      }

      @Bean
      @ConditionalOnMissingBean
      public RestTemplateCustomizer restTemplateCustomizer(
            final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
         return new RestTemplateCustomizer() {
            @Override
            public void customize(RestTemplate restTemplate) {
               List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                     restTemplate.getInterceptors());
               list.add(loadBalancerInterceptor);
               restTemplate.setInterceptors(list);
            }
         };
      }
   }
}

通过此Configuration头部的

@ConditionalOnClass(RestTemplate.class)

@ConditionalOnBean(LoadBalancerClient.class)

这两个注解可知当restTemplate存在于这个JAR包中且LoadBalancerClient存在于当前spring容器中时LoadBalancerAutoConfiguration 便会进行如下的主要配置:

List<ClientHttpRequestInterceptor> list = new ArrayList<>(
      restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);

往restTemplate的拦截器中添加一个LoadBalancerInterceptor

所以LoadBalancerInterceptor是在LoadBalancerAutoConfiguration中添加的

…………

总结

通过源码跟踪知道了restTemplate能通过服务名获取到具体的服务是由LoadBalancerInterceptor这个拦截器实现的,而具体的是由RibbonLoadBalancerClient来实现的。RibbonLoadBalancerClient将服务名通过负载均衡策略转为了实际的ip和端口后再apply给restTemplate。

在跟踪此源码的过程中发现还有好些个问题值得去探究,如loadBanlancer的具体实现操作是怎么样的?service的列表是何时加载到这个框架的?

革命尚未成功同志仍努力


  • 31
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
SpringCloudRestTemplate是一个用于发送HTTP请求的客户端工具。它可以用于与其他微服务进行通信。在Spring CloudRestTemplate有三种使用方式: 1. 直接访问IP和端口:可以直接使用RestTemplate发送HTTP请求到指定的服务IP和端口,这是一种简单直接的方式。 2. 使用注册心和负载均衡:通过整合注册心(如Eureka)和负载均衡组件(如Ribbon),RestTemplate可以自动根据负载均衡策略选择目标微服务实例,并发送请求。可以手动配置负载均衡策略,也可以使用@LoadBalanced注解实现自动负载均衡。 3. 使用Nacos作为注册心和负载均衡:Nacos是阿里巴巴开源的一款服务发现与配置管理工具,可以作为注册心和负载均衡组件。RestTemplate可以与Nacos配合使用,实现服务发现和负载均衡的功能。 通过引入spring-boot-starter-web依赖,我们可以使用RestTemplate来发送HTTP请求,并且结合Spring Cloud的各种功能来实现微服务之间的通信。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【SpringCloudspringcloudRestTemplate三种使用方式(LoadBalancerClient、负载均衡、Nacos、Ribbon )](https://blog.csdn.net/m0_45406092/article/details/118482401)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水中加点糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值