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.
 * 

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 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, List> entry : request.getHeaders().entrySet()) {
            List 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 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)时已经获取到服务器具体信息了,所以一切豁然开朗了吧!

Spring Cloud中restTemplate是如何通过服务名主求到具体服务的?_第1张图片

获取服务名的关键代码为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 restTemplates = Collections.emptyList();

   @Bean
   public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
         final List 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 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 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 list = new ArrayList<>(
                     restTemplate.getInterceptors());
               list.add(loadBalancerInterceptor);
               restTemplate.setInterceptors(list);
            }
         };
      }
   }
}

通过此Configuration头部的

@ConditionalOnClass(RestTemplate.class)

@ConditionalOnBean(LoadBalancerClient.class)

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

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

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

所以LoadBalancerInterceptor是在LoadBalancerAutoConfiguration中添加的

…………

总结

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

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

革命尚未成功同志仍努力


你可能感兴趣的:(开源框架学习)