SpringCloud解析二:Ribbon源碼分析(上)

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)系如下所示


ClientHttpRequestInterceptor繼承關(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)系


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è)流程

  1. 收集所有被@LoadBalanced注解標(biāo)注的RestTemplate對(duì)象

  2. 使用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的情況

  1. 使用LoadBalancerClient與LoadBalancerRequestFactory一起作為構(gòu)造參數(shù)創(chuàng)建LoadBalancerInterceptor實(shí)例

  2. 構(gòu)建RestTemplateCustomizer對(duì)象咖气,將LoadBalancerInterceptor添加到RestTemplate的Interceptors中

情景二:引入RetryTemplate的情況

  1. 如果當(dāng)前環(huán)境中沒有LoadBalancedRetryFactory類型的Bean挖滤,則實(shí)例化LoadBalancedRetryFactory對(duì)象【在Ribbon中會(huì)創(chuàng)建LoadBalancedRetryFactory實(shí)例對(duì)象】

  2. 創(chuàng)建RetryLoadBalancerInterceptor實(shí)例對(duì)象【LoadBalancerClient, LoadBalancerRetryProperties, LoadBalancerRequestFactory斩松,LoadBalancedRetryFactory作為構(gòu)造參數(shù)】

  3. 構(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)行訪問就可以看到打印的日志了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畏梆,一起剝皮案震驚了整個(gè)濱河市奈懒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌溜畅,老刑警劉巖极祸,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贿肩,死亡現(xiàn)場離奇詭異龄寞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)溜哮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門色解,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人述吸,你說我怎么就攤上這事〉琅” “怎么了入撒?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長璃赡。 經(jīng)常有香客問我献雅,道長,這世上最難降的妖魔是什么豆励? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任瞒渠,我火速辦了婚禮,結(jié)果婚禮上嫩痰,老公的妹妹穿的比我還像新娘窍箍。我一直安慰自己,他們只是感情好纺棺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布邪狞。 她就那樣靜靜地躺著,像睡著了一般巨朦。 火紅的嫁衣襯著肌膚如雪剑令。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天棚蓄,我揣著相機(jī)與錄音,去河邊找鬼挣柬。 笑死睛挚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的侧到。 我是一名探鬼主播淤击,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汞贸!你這毒婦竟也來了印机?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤多柑,失蹤者是張志新(化名)和其女友劉穎楣责,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體初嘹,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沮趣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年兔毒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡豪嗽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出龟梦,到底是詐尸還是另有隱情,我是刑警寧澤钦睡,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布荞怒,位于F島的核電站秧秉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏象迎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一啦撮、第九天 我趴在偏房一處隱蔽的房頂上張望汪厨。 院中可真熱鬧,春花似錦聘鳞、人聲如沸要拂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拉一。三九已至,卻和暖如春磅氨,著一層夾襖步出監(jiān)牢的瞬間嫡纠,已是汗流浹背延赌。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國打工叉橱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窃祝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓大磺,卻偏偏與公主長得像糕再,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子突想,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容