Spring Cloud Ribbon(后面簡稱Ribbon)是一個(gè)基于Netfix Ribbon實(shí)現(xiàn)的用于HTTP與TCP的客戶端負(fù)載均衡工具盐杂。經(jīng)過Spring Cloud的封裝后,可以輕松的通過RestTemplate來實(shí)現(xiàn)服務(wù)的客戶端負(fù)載均衡的調(diào)用厉斟。
一:概覽
在使用Ribbon的時(shí)候强衡,我們只需要經(jīng)過簡單的幾個(gè)步驟即可使用【本章節(jié)基于Spring Cloud的Hoxton.RELEASE版本進(jìn)行分析】
1漩勤,配置依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
2,將RestTemplate實(shí)例使用@LoadBalanced注解標(biāo)注
@Bean // 初始化 Bean
@LoadBalanced // 實(shí)現(xiàn)負(fù)載均衡
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate ;
}
3触幼,在配置文件中添加必要的配置
########指定客戶端配置【覆蓋全局配置】########
<clientName>.ribbon.listOfServers=ip1:port,ip2:port #服務(wù)所擁有的訪問地址
<clientName>.ribbon.ConnectTimeout=250 #請(qǐng)求連接的超時(shí)時(shí)間
<clientName>.ribbon.ReadTimeout=1000 #請(qǐng)求處理的超時(shí)時(shí)間
<clientName>.ribbon.OkToRetryOnAllOperations=false #對(duì)所有的操作請(qǐng)求都進(jìn)行重試[只對(duì)GET請(qǐng)求重試]
<clientName>.ribbon.MaxAutoRetriesNextServer=2 #切換實(shí)例的重試次數(shù)
<clientName>.ribbon.MaxAutoRetries=1 #對(duì)當(dāng)前實(shí)例的重試次數(shù)
spring.cloud.loadbalancer.retry.enable=true #是否開啟重試機(jī)制【默認(rèn)開啟】
########全局配置(缺少clientName)[適用于所有客戶端]########
ribbon.ConnectTimeout=2000 #請(qǐng)求連接的超時(shí)時(shí)間,默認(rèn)2000
ribbon.ReadTimeout=5000 #請(qǐng)求處理的超時(shí)時(shí)間,默認(rèn)5000
ribbon.MaxAutoRetries=0 #對(duì)當(dāng)前實(shí)例的重試次數(shù),默認(rèn)0
ribbon.MaxAutoRetriesNextServer=1 #切換實(shí)例的重試次數(shù),默認(rèn)1
ribbon.OkToRetryOnAllOperations=false #對(duì)所有的操作請(qǐng)求都進(jìn)行重試[為false時(shí)只對(duì)GET請(qǐng)求重試],默認(rèn)false
ribbon.retryableStatusCodes=500,501 #進(jìn)行重試判斷的響應(yīng)狀態(tài)碼,多個(gè)逗號(hào)分隔【默認(rèn)為空】
ribbon.listOfServers=ip3:port,ip4:port #服務(wù)所擁有的訪問地址
########Ribbon 客戶端行為配置########
ribbon.eager-load.enabled=flase #是否開啟饑餓加載域蜗,即初始化就創(chuàng)建Ribon客戶端而非等到第一次訪問
ribbon.eager-load.clients=<clientName-1>,<clientName-2> #開啟饑餓加載的客戶端名,多個(gè)逗號(hào)分隔
其中的<clientName>需要替換成真正的服務(wù)名稱,listOfServers后面的值為該服務(wù)所對(duì)應(yīng)的訪問地址【實(shí)例清單】筑累,多個(gè)用英文逗號(hào)分隔丝蹭。完整的格式含義:
<clientName>.<nameSpace>.<propertyName>=<value>
其nameSpace的默認(rèn)值為ribbon奔穿。
當(dāng)環(huán)境中引入了spring-retry包的時(shí)候會(huì)自動(dòng)構(gòu)建具備重試功能的攔截器(否則為不具備重試功能的攔截器),然后通過參數(shù)spring.cloud.loadbalancer.retry.enable可以控制是否開啟重試功能【這個(gè)都是在comons中定義的缅茉,如果要集成類似Ribbon男摧,只需要自定義一個(gè)LoadBalancedRetryFactory類型的Bean即可,然后就可以自定義自己使用的各種參數(shù)了】拇颅。
引入spring-retry從而配置具備重試功能的RestTemplate攔截器
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
更多的默認(rèn)值請(qǐng)參考:com.netflix.client.config.DefaultClientConfigImpl.java
4樟插,使用RestTemplate實(shí)例發(fā)起服務(wù)的調(diào)用
restTemplate.getForObject("http://server-name/path/to/query",
String.class, new HashMap<String,String>()) ;
二:源碼解析
接下來我們通過源碼來進(jìn)一步分析其實(shí)現(xiàn)原理竿刁,首先我們需要關(guān)注的是依賴的spring-cloud-commons-2.2.0.RELEASE.jar
1,spring-cloud-commons-2.2.0.RELEASE
Spring Cloud Commons是一組抽象和公共類鸵熟,用于不同的Spring Cloud實(shí)現(xiàn)(比如Spring Cloud Netflix和Spring Cloud Consul)旅赢。
在該包的/META-INF/spring.factories中配置了一個(gè)關(guān)于負(fù)載均衡的啟動(dòng)類惑惶,如下所示
# AutoConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
org.springframework.cloud.client.ReactiveCommonsClientAutoConfiguration,\
#配置ApacheHttpClient(默認(rèn))或者OkHttpClient
org.springframework.cloud.commons.httpclient.HttpClientConfiguration,\
org.springframework.cloud.client.loadbalancer.AsyncLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerBeanPostProcessorAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancerAutoConfiguration,\
org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerClientAutoConfiguration
我們從非響應(yīng)式的配置類LoadBalancerAutoConfiguration入手分析其啟動(dòng)過程(對(duì)于異步的AsyncLoadBalancerAutoConfiguration與此類似)
@Configuration(proxyBeanMethods = false)
//RestTemplate類位于類路徑上時(shí)滿足條件
@ConditionalOnClass(RestTemplate.class)
//LoadBalancerClient類型的Bean包含在BeanFactory中時(shí)滿足條件
@ConditionalOnBean(LoadBalancerClient.class)
//讀取spring.cloud.loadbalancer.retry.enable配置的值
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {
//注入當(dāng)前IOC環(huán)境中所有被@LoadBalanced注解標(biāo)注的RestTemplate實(shí)例對(duì)象
@LoadBalanced
@Autowired(required = false)
private List<RestTemplate> restTemplates = Collections.emptyList();
@Autowired(required = false)
private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
//應(yīng)用啟動(dòng)后带污,收集當(dāng)前IOC中所有的RestTemplateCustomizer對(duì)象,并使用該對(duì)象對(duì)RestTemplate進(jìn)行定制操作
//通過這種方式給RestTemplate設(shè)置攔截器等
@Bean
public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
return () -> restTemplateCustomizers.ifAvailable(customizers -> {
for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
for (RestTemplateCustomizer customizer : customizers) {
customizer.customize(restTemplate);
}
}
});
}
/**
* 如果當(dāng)前環(huán)境中沒有LoadBalancerRequestFactory對(duì)象則創(chuàng)建
*
* 注意:這對(duì)象并不是RestTemplate里面使用的ClientHttpRequestFactory類型的對(duì)象报破,
* 而只是一個(gè)擁有LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,
* final byte[] body,final ClientHttpRequestExecution execution)
* 方法的普通對(duì)象充易。 可看成一個(gè)工具類而已。
*/
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(
LoadBalancerClient loadBalancerClient) {
return new LoadBalancerRequestFactory(loadBalancerClient, this.transformers);
}
////////////以下是配置不具備重試功能的攔截器//////////////
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
static class LoadBalancerInterceptorConfig {
//配置攔截器
@Bean
public LoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRequestFactory requestFactory) {
//通過該ClientHttpRequestInterceptor類型的攔截器來實(shí)現(xiàn)客戶端負(fù)載均衡功能
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
//給RestTemplate設(shè)置攔截器
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
////////////以下是配置具備重試功能的攔截器//////////////
/**
* 如果在當(dāng)前環(huán)境中能找到RetryTemplate類炸茧,則使用以下方式進(jìn)行配置[目的是讓其支持重試功能]
* 需要引入spring-retry包
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
/**
* 如果在當(dāng)前環(huán)境中能找到RetryTemplate類梭冠,則使用以下方式進(jìn)行配置攔截器[目的是讓其支持重試功能]
* 需要引入spring-retry包
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RetryTemplate.class)
public static class RetryInterceptorAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RetryLoadBalancerInterceptor ribbonInterceptor(
LoadBalancerClient loadBalancerClient,
LoadBalancerRetryProperties properties,
LoadBalancerRequestFactory requestFactory,
LoadBalancedRetryFactory loadBalancedRetryFactory) {
return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
requestFactory, loadBalancedRetryFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
}
從上面的源碼可知控漠,只要當(dāng)前環(huán)境中具備LoadBalancerClient類型的Bean悬钳,那么就會(huì)自動(dòng)給RestTemplate添加上攔截器,從而達(dá)到負(fù)載均衡的目的毙驯。而具體怎么做負(fù)載均衡控制灾测,都是在LoadBalancerClient的實(shí)現(xiàn)類中完成媳搪。為了進(jìn)一步探究其執(zhí)行過程骤宣,我們先來分析下LoadBalancerInterceptor對(duì)象,該對(duì)象的繼承關(guān)系如下所示
LoadBalancerInterceptor的源碼:
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));
}
//該方法在RestTemplate中的InterceptingClientHttpRequest對(duì)象中被調(diào)用等限,可參考前面章節(jié)的介紹
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
//取出URI中的host作為服務(wù)名
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
//執(zhí)行LoadBalancerClient實(shí)現(xiàn)類的execute方法并返回結(jié)果
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
可見望门,其LoadBalancerInterceptor 攔截器的作用就是為了調(diào)用LoadBalancerClient實(shí)例所提供的execute方法而已锰霜。
其中癣缅,LoadBalancerRequestFactory源碼如下
public class LoadBalancerRequestFactory {
private LoadBalancerClient loadBalancer;
private List<LoadBalancerRequestTransformer> transformers;
public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer,
List<LoadBalancerRequestTransformer> transformers) {
this.loadBalancer = loadBalancer;
this.transformers = transformers;
}
public LoadBalancerRequestFactory(LoadBalancerClient loadBalancer) {
this.loadBalancer = loadBalancer;
}
//創(chuàng)建一個(gè)LoadBalancerRequest<T>類型的對(duì)象哄酝,返回后供LoadBalancerClient 使用
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
//創(chuàng)建一個(gè)匿名的LoadBalancerRequest類型的實(shí)例對(duì)象
//這里的instance為ServiceInstance類型的對(duì)象(即被選擇的服務(wù)對(duì)象)
return instance -> {
//通過包裝器來對(duì)請(qǐng)求進(jìn)行處理
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
//繼續(xù)執(zhí)行其他的攔截器
return execution.execute(serviceRequest, body);
};
}
}
重點(diǎn):LoadBalancerClient 的execute方法需要接收一個(gè)LoadBalancerRequest類型的實(shí)例對(duì)象陶衅,至于該對(duì)象在何處創(chuàng)建并不重要直晨。
ServiceRequestWrapper源碼如下抡秆。這里主要是對(duì)getURI重寫,通過LoadBalancer來解析得出具體的url地址
public class ServiceRequestWrapper extends HttpRequestWrapper {
private final ServiceInstance instance;
private final LoadBalancerClient loadBalancer;
public ServiceRequestWrapper(HttpRequest request, ServiceInstance instance,
LoadBalancerClient loadBalancer) {
super(request);
this.instance = instance;
this.loadBalancer = loadBalancer;
}
@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}
}
其中的LoadBalancerRequest源碼如下
public interface LoadBalancerRequest<T> {
T apply(ServiceInstance instance) throws Exception;
}
到此為止就是spring-cloud-commons-2.2.0.RELEASE為我們定義的使用RestTemplate做客戶端負(fù)載均衡的處理邏輯的止,我們只需要在IOC中具備LoadBalancerClient實(shí)現(xiàn)類即可着撩。接著我們看下LoadBalancerClient的繼承關(guān)系
public interface ServiceInstanceChooser {
/**
* Chooses a ServiceInstance from the LoadBalancer for the specified service.
* @param serviceId The service ID to look up the LoadBalancer.
* @return A ServiceInstance that matches the serviceId.
*/
ServiceInstance choose(String serviceId);
}
public interface LoadBalancerClient extends ServiceInstanceChooser {
/**
* 在RestTemplate的攔截器中調(diào)用的方法
* Executes request using a ServiceInstance from the LoadBalancer for the specified
* service.
* @param serviceId The service ID to look up the LoadBalancer.
* @param request Allows implementations to execute pre and post actions, such as
* incrementing metrics.
* @param <T> type of the response
* @throws IOException in case of IO issues.
* @return The result of the LoadBalancerRequest callback on the selected
* ServiceInstance.
*/
<T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException;
/**
* Executes request using a ServiceInstance from the LoadBalancer for the specified
* service.
* @param serviceId The service ID to look up the LoadBalancer.
* @param serviceInstance The service to execute the request to.
* @param request Allows implementations to execute pre and post actions, such as
* incrementing metrics.
* @param <T> type of the response
* @throws IOException in case of IO issues.
* @return The result of the LoadBalancerRequest callback on the selected
* ServiceInstance.
*/
<T> T execute(String serviceId, ServiceInstance serviceInstance,
LoadBalancerRequest<T> request) throws IOException;
/**
* Creates a proper URI with a real host and port for systems to utilize. Some systems
* use a URI with the logical service name as the host, such as
* http://myservice/path/to/service. This will replace the service name with the
* host:port from the ServiceInstance.
* @param instance service instance to reconstruct the URI
* @param original A URI with the host as a logical service name.
* @return A reconstructed URI.
*/
URI reconstructURI(ServiceInstance instance, URI original);
}
小結(jié)
在這里總結(jié)下上面的整個(gè)流程
收集所有被@LoadBalanced注解標(biāo)注的RestTemplate對(duì)象
使用LoadBalancerClient類型的對(duì)象作為構(gòu)造參數(shù)來創(chuàng)建LoadBalancerRequestFactory實(shí)例(是個(gè)普通的類氓润,其createRequest方法返回LoadBalancerRequest類型實(shí)例對(duì)象)
public LoadBalancerRequest<ClientHttpResponse> createRequest(
final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) {
return instance -> {
HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,
this.loadBalancer);
if (this.transformers != null) {
for (LoadBalancerRequestTransformer transformer : this.transformers) {
serviceRequest = transformer.transformRequest(serviceRequest,
instance);
}
}
return execution.execute(serviceRequest, body);
};
}
情景一:沒有引入RetryTemplate的情況
使用LoadBalancerClient與LoadBalancerRequestFactory一起作為構(gòu)造參數(shù)創(chuàng)建LoadBalancerInterceptor實(shí)例
構(gòu)建RestTemplateCustomizer對(duì)象咖气,將LoadBalancerInterceptor添加到RestTemplate的Interceptors中
情景二:引入RetryTemplate的情況
如果當(dāng)前環(huán)境中沒有LoadBalancedRetryFactory類型的Bean挖滤,則實(shí)例化LoadBalancedRetryFactory對(duì)象【在Ribbon中會(huì)創(chuàng)建LoadBalancedRetryFactory實(shí)例對(duì)象】
創(chuàng)建RetryLoadBalancerInterceptor實(shí)例對(duì)象【LoadBalancerClient, LoadBalancerRetryProperties, LoadBalancerRequestFactory斩松,LoadBalancedRetryFactory作為構(gòu)造參數(shù)】
構(gòu)建RestTemplateCustomizer對(duì)象,將RetryLoadBalancerInterceptor添加到RestTemplate的Interceptors中
通過上面的步驟完成了對(duì)RestTemplate攔截器的創(chuàng)建與添加工作乳幸,最后當(dāng)執(zhí)行RestTemplate的方法進(jìn)行HTTP請(qǐng)求時(shí)钧椰,其添加攔截器的intercept方法將會(huì)被調(diào)用【ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) 】。
如果是LoadBalancerInterceptor攔截器
1)取出URI中的Host作為服務(wù)名(serviceName)
2)調(diào)用requestFactory的createRequest方法得到LoadBalancerRequest類型對(duì)象
3)使用serviceName和LoadBalancerRequest做為參數(shù)調(diào)用LoadBalancerClient的execute方法并將結(jié)果返回
如果是RetryLoadBalancerInterceptor攔截器
1)取出URI中的Host作為服務(wù)名(serviceName)
2)創(chuàng)建重試策略-LoadBalancedRetryPolicy
3)創(chuàng)建RetryTemplate對(duì)象
4)然后執(zhí)行RetryTemplate的execute方法并將結(jié)果返回
這里主要介紹了spring-cloud-commons包中給我們定義的使用RestTemplate進(jìn)行負(fù)載均衡訪問的方法姿染,我們只需要自定義一個(gè)LoadBalancerClient的實(shí)現(xiàn)類,然后在實(shí)現(xiàn)類中完成負(fù)載均衡算法即可狡汉。
在后面的章節(jié)將繼續(xù)解釋Ribbon與RestTemplate的整合過程闽颇。
自定義LoadBalancerClient 以及使用舉例:
public class CustomLoadBalancerClient implements LoadBalancerClient {
public CustomLoadBalancerClient () {
System.out.print("我被實(shí)例化了:" + CustomLoadBalancerClient.class.getName());
}
public static class CustomServiceInstance implements ServiceInstance {
private final String serviceId;
private String host;
private int port = 80;
private String scheme;
private String instanceId;
private Map<String, String> metadata;
public CustomServiceInstance(String serviceId, String instanceId, String host, int port, String scheme,
Map<String, String> metadata) {
this.serviceId = serviceId;
this.instanceId = instanceId;
this.host = host;
this.port = port;
this.scheme = scheme;
this.metadata = metadata;
}
@Override
public String getInstanceId() {
return this.instanceId;
}
@Override
public String getServiceId() {
return this.serviceId;
}
@Override
public String getHost() {
return this.host;
}
@Override
public int getPort() {
return this.port;
}
@Override
public URI getUri() {
return DefaultServiceInstance.getUri(this);
}
@Override
public Map<String, String> getMetadata() {
return this.metadata;
}
@Override
public String getScheme() {
return this.scheme;
}
@Override
public boolean isSecure() {
return false;
}
}
/**
* 重寫方法尖啡,進(jìn)行服務(wù)的篩選
* */
@Override
public ServiceInstance choose(String serviceId) {
System.out.println("需要進(jìn)行選擇的服務(wù)ID為:" + serviceId);
String instanceId = "instance-1001";
String host = "192.168.30.161";
int port = 8000;
String scheme = "http";
Map<String, String> metadata = null ;
return new CustomServiceInstance(serviceId, instanceId, host, port, scheme, metadata);
}
/**
* 被LoadBalancerInterceptor攔截器所調(diào)用的方法
* */
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
System.out.println("我被執(zhí)行了哦:" + this.getClass().getName() + " -- > execute");
ServiceInstance serviceInstance = this.choose(serviceId) ;
return execute(serviceId, serviceInstance, request);
}
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request)
throws IOException {
try {
T returnVal = request.apply(serviceInstance);
return returnVal;
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
/**
* @param instance 經(jīng)過篩選后要訪問的服務(wù)實(shí)例信息
* @param original 原始訪問路徑
*/
@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
Assert.notNull(instance, "instance can not be null");
String scheme = instance.getScheme();
String host = instance.getHost();
int port = instance.getPort();
return reconstructURIWithServer(scheme, host, port, original);
}
public URI reconstructURIWithServer(String scheme, String host, int port, URI original) {
if (host.equals(original.getHost()) && port == original.getPort() && scheme == original.getScheme()) {
return original;
}
if (scheme == null) {
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);
}
}
protected Pair<String, Integer> deriveSchemeAndPortFromPartialUri(URI uri) {
boolean isSecure = false;
String scheme = uri.getScheme();
if (scheme != null) {
isSecure = scheme.equalsIgnoreCase("https");
}
int port = uri.getPort();
if (port < 0 && !isSecure) {
port = 80;
} else if (port < 0 && isSecure) {
port = 443;
}
if (scheme == null) {
if (isSecure) {
scheme = "https";
} else {
scheme = "http";
}
}
return new Pair<String, Integer>(scheme, port);
}
public class Pair<E1, E2> implements Serializable {
private static final long serialVersionUID = 2L;
private E1 mFirst;
private E2 mSecond;
public Pair(E1 first, E2 second) {
mFirst = first;
mSecond = second;
}
public E1 first() {
return mFirst;
}
public E2 second() {
return mSecond;
}
public void setFirst(E1 first) {
mFirst = first;
}
public void setSecond(E2 second) {
mSecond = second;
}
}
}
然后添加配置
@Configuration
@AutoConfigureBefore({RibbonAutoConfiguration.class })
public class CustomLoadBalancerClientConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate ;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new CustomLoadBalancerClient();
}
}
這時(shí)使用RestTemplate進(jìn)行訪問就可以看到打印的日志了。