最近的项目是基于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; }
一直跟进代码,直到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 Iteratoriterator; 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 ; for (String value : values) { delegate.getHeaders().add(entry.getKey(), value); } } if (body.length > 0) { StreamUtils.copy(body, delegate.getBody()); } return delegate.execute(); } } }values = entry.getValue()
这里的InterceptingRequestExecution 类是一个内部类,位于主类InterceptingClientHttpRequest中。从InterceptingRequestExecution 这个类中可以看出这个类中唯一成员变量iterator是由其主类的interceptors获取的。那么主类中的iterceptors又是在哪里设置的呢? 这里先不管,待会再看。
class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { private final ClientHttpRequestFactory requestFactory; private final Listinterceptors;
继续跟着主线的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方法。
继续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中的?这个我一开始也不知道,后来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 ListrestTemplates = 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 Listtransformers = 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) { Listlist = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } } }
通过此Configuration头部的
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
这两个注解可知当restTemplate存在于这个JAR包中且LoadBalancerClient存在于当前spring容器中时LoadBalancerAutoConfiguration 便会进行如下的主要配置:
Listlist = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list);
往restTemplate的拦截器中添加一个LoadBalancerInterceptor
所以LoadBalancerInterceptor是在LoadBalancerAutoConfiguration中添加的
…………
通过源码跟踪知道了restTemplate能通过服务名获取到具体的服务是由LoadBalancerInterceptor这个拦截器实现的,而具体的是由RibbonLoadBalancerClient来实现的。RibbonLoadBalancerClient将服务名通过负载均衡策略转为了实际的ip和端口后再apply给restTemplate。
在跟踪此源码的过程中发现还有好些个问题值得去探究,如loadBanlancer的具体实现操作是怎么样的?service的列表是何时加载到这个框架的?
革命尚未成功,同志仍需努力