【SpringCloud】Ribbon如何自定義客戶端配置和全局配置

起因

事情的起因是這樣的,公司內(nèi)部要實(shí)現(xiàn)基于Zuul網(wǎng)關(guān)的灰度路由,在上線時(shí)進(jìn)行灰度測(cè)試,故需要配置業(yè)務(wù)微服務(wù)向Eureka注冊(cè)的metadata元數(shù)據(jù),和自定義Ribbon的負(fù)載規(guī)則達(dá)到只訪問(wèn)灰度服務(wù)的目的。這樣就需要自定義Ribbon的IRule,實(shí)現(xiàn)灰度請(qǐng)求只會(huì)負(fù)載到帶有灰度標(biāo)簽元數(shù)據(jù)的業(yè)務(wù)微服務(wù)上,當(dāng)自定義IRule規(guī)則開發(fā)好后,問(wèn)題是如何將這個(gè)IRule規(guī)則配置給某個(gè)Ribbon Client或者全局生效。

本次使用Spring Cloud Dalston.SR5版本

在其 官方文檔 中其實(shí)已經(jīng)給出了一些如何針對(duì)某個(gè)Client 或者 修改默認(rèn)配置的方式,但沒有說(shuō)明為什么這樣使用

下面將按照這樣的思路分析:

  • 簡(jiǎn)單分析Spring Cloud Ribbon啟動(dòng)時(shí)如何自動(dòng)配置的,以了解其裝配到Spring中的Bean
  • Spring Cloud Ribbon Client的懶加載
  • Spring Cloud Ribbon Client的配置加載,包含全局配置及Client配置
  • 如何自定義Client配置、全局配置
  • 解釋官方文檔中的一些注意事項(xiàng)


Spring Cloud Ribbon自動(dòng)配置

當(dāng)前版本中的Netflix所有自動(dòng)配置都在spring-cloud-netflix-core-xxx.jar中,根據(jù)其META-INF/spring.factories中的配置得知,Spring Cloud Ribbon的自動(dòng)配置類為 RibbonAutoConfiguration

spring-cloud-ribbon-autoconfiguration-spring-factories.jpg


RibbonAutoConfiguration

@Configuration
@ConditionalOnClass({ IClient.class, RestTemplate.class, AsyncRestTemplate.class, Ribbon.class})
@RibbonClients
@AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties(RibbonEagerLoadProperties.class)
public class RibbonAutoConfiguration {

    // 所有針對(duì)某個(gè)RibbonClient指定的配置
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();
    
    // ribbon是否懶加載的配置文件
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    // Spring會(huì)給每個(gè)RibbonClient創(chuàng)建獨(dú)立的ApplicationContext上下文
    // 并在其上下文中創(chuàng)建RibbonClient對(duì)應(yīng)的Bean:如IClient、ILoadbalancer等
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }

    // Spring創(chuàng)建的帶負(fù)載均衡功能的Client,會(huì)使用SpringClientFactory創(chuàng)建對(duì)應(yīng)的Bean和配置
    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

    // 到Spring environment中加載針對(duì)某個(gè)Client的Ribbon的核心接口實(shí)現(xiàn)類
    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }
    
    // 如果不是懶加載,啟動(dòng)時(shí)就使用RibbonApplicationContextInitializer加載并初始化客戶端配置
    @Bean
    @ConditionalOnProperty(value = "ribbon.eager-load.enabled", matchIfMissing = false)
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }

    ......
}

上面RibbonAutoConfiguration創(chuàng)建的Bean主要分以下幾類:

  • 為Ribbon Client創(chuàng)建環(huán)境及獲取配置
    • SpringClientFactory: 會(huì)給每個(gè)Ribbon Client創(chuàng)建一個(gè)獨(dú)立的Spring應(yīng)用上下文ApplicationContext,并在其中加載對(duì)應(yīng)的配置及Ribbon核心接口的實(shí)現(xiàn)類
    • PropertiesFactory: 用于從Spring enviroment環(huán)境中獲取針對(duì)某個(gè)Ribbon Client配置的核心接口實(shí)現(xiàn)類,并實(shí)例化
  • 創(chuàng)建RibbonLoadBalancerClient,并將springClientFactory注入,方便從中獲取對(duì)應(yīng)的配置及實(shí)現(xiàn)類,RibbonLoadBalancerClient是Spring對(duì)LoadBalancerClient接口的實(shí)現(xiàn)類,其execute()方法提供客戶端負(fù)載均衡能力
  • 懶加載相關(guān)
    • RibbonEagerLoadProperties: 懶加載配置項(xiàng)Properties,可以指定是否懶加載,及哪些Client不懶加載
    • RibbonApplicationContextInitializer: 啟動(dòng)時(shí)就加載RibbonClient配置(非懶加載)的初始化器

可以看到默認(rèn)啟動(dòng)流程中并沒有加載RibbonClient的上下文和配置信息,而是在使用時(shí)才加載,即懶加載


Spring Cloud RibbonClient的懶加載

既然是在使用時(shí)才會(huì)加載,那么以Zuul網(wǎng)關(guān)為例,在其RibbonRoutingFilter中會(huì)創(chuàng)建RibbonCommand,其包含了Ribbon的負(fù)載均衡

//## RibbonRoutingFilter  Zuul負(fù)責(zé)路由的Filter
public class RibbonRoutingFilter extends ZuulFilter {

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        this.helper.addIgnoredHeaders();
        try {
            RibbonCommandContext commandContext = buildCommandContext(context);
            ClientHttpResponse response = forward(commandContext);
            setResponse(response);
            return response;
        }
        catch (ZuulException ex) {
            throw new ZuulRuntimeException(ex);
        }
        catch (Exception ex) {
            throw new ZuulRuntimeException(ex);
        }
    }

    protected ClientHttpResponse forward(RibbonCommandContext context) throws Exception {
        Map<String, Object> info = this.helper.debug(context.getMethod(),
                context.getUri(), context.getHeaders(), context.getParams(),
                context.getRequestEntity());

        // 使用ribbonCommandFactory創(chuàng)建RibbonCommand
        RibbonCommand command = this.ribbonCommandFactory.create(context);
        try {
            ClientHttpResponse response = command.execute();
            this.helper.appendDebug(info, response.getStatusCode().value(),
                    response.getHeaders());
            return response;
        }
        catch (HystrixRuntimeException ex) {
            return handleException(info, ex);
        }
    }
}

在執(zhí)行RibbonRoutingFilter#run()進(jìn)行路由時(shí)會(huì)執(zhí)行forward()方法,由于此處是在HystrixCommand內(nèi)部執(zhí)行Ribbon負(fù)載均衡調(diào)用,故使用ribbonCommandFactory創(chuàng)建RibbonCommand,Ribbon客戶端的懶加載就在這個(gè)方法內(nèi),這里我們看HttpClientRibbonCommandFactory實(shí)現(xiàn)類

//## org.springframework.cloud.netflix.zuul.filters.route.apache.HttpClientRibbonCommandFactory
public class HttpClientRibbonCommandFactory extends AbstractRibbonCommandFactory {
    @Override
    public HttpClientRibbonCommand create(final RibbonCommandContext context) {
        ZuulFallbackProvider zuulFallbackProvider = getFallbackProvider(context.getServiceId());
        final String serviceId = context.getServiceId();
        // 通過(guò)SpringClientFactory獲取IClient接口實(shí)例
        final RibbonLoadBalancingHttpClient client = this.clientFactory.getClient(
                serviceId, RibbonLoadBalancingHttpClient.class);
        client.setLoadBalancer(this.clientFactory.getLoadBalancer(serviceId));

        return new HttpClientRibbonCommand(serviceId, client, context, zuulProperties, zuulFallbackProvider,
                clientFactory.getClientConfig(serviceId));
    }
}

創(chuàng)建RibbonLoadBalancingHttpClient的邏輯在 SpringClientFactory#getClient(serviceId, RibbonLoadBalancingHttpClient.class),如下:

  • SpringClientFactory#getInstance(name, clientClass)
    • NamedContextFactory#getInstance(name, type):
      • 獲取Client對(duì)應(yīng)的ApplicationContext,如沒有則調(diào)用createContext()創(chuàng)建,其中包含注冊(cè)統(tǒng)一默認(rèn)配置類RibbonClientConfiguration,或@RibbonClient、@RibbonClients(defaultConfiguration=xxx) 設(shè)置的配置類的邏輯
      • 從ApplicationContext中根據(jù)類型獲取實(shí)例,如沒有使用反射創(chuàng)建,并通過(guò)IClientConfig配置

如上執(zhí)行完畢RibbonClient就基本懶加載完成了,就可以到RibbonClient對(duì)應(yīng)的ApplicationContext中繼續(xù)獲取其它核心接口的實(shí)現(xiàn)類了,這些實(shí)現(xiàn)類都是根據(jù) 默認(rèn)/全局/Client自定義 配置創(chuàng)建的

//## org.springframework.cloud.netflix.ribbon.SpringClientFactory
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
    static final String NAMESPACE = "ribbon";

    public SpringClientFactory() {
        super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
    }
    
    /**
     * Get the rest client associated with the name.
     * @throws RuntimeException if any error occurs
     */
    public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
        return getInstance(name, clientClass);
    }
    
    // name代表當(dāng)前Ribbon客戶端,type代表要獲取的實(shí)例類型,如IClient、IRule
    @Override
    public <C> C getInstance(String name, Class<C> type) {
        // 先從父類NamedContextFactory中直接從客戶端對(duì)應(yīng)的ApplicationContext中獲取實(shí)例
        // 如果沒有就根據(jù)IClientConfig中的配置找到具體的實(shí)現(xiàn)類,并通過(guò)反射初始化后放到Client對(duì)應(yīng)的ApplicationContext中
        C instance = super.getInstance(name, type);
        if (instance != null) {
            return instance;
        }
        IClientConfig config = getInstance(name, IClientConfig.class);
        return instantiateWithConfig(getContext(name), type, config);
    }
    
    // 使用IClientConfig實(shí)例化
    static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
                                        Class<C> clazz, IClientConfig config) {
        C result = null;
        try {
            // 通過(guò)以IClientConfig為參數(shù)的構(gòu)造創(chuàng)建clazz類實(shí)例
            Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
            result = constructor.newInstance(config);
        } catch (Throwable e) {
            // Ignored
        }
        
        // 如果沒創(chuàng)建成功,使用無(wú)慘構(gòu)造
        if (result == null) {
            result = BeanUtils.instantiate(clazz);
            
            // 調(diào)用初始化配置方法
            if (result instanceof IClientConfigAware) {
                ((IClientConfigAware) result).initWithNiwsConfig(config);
            }
            
            // 處理自動(dòng)織入
            if (context != null) {
                context.getAutowireCapableBeanFactory().autowireBean(result);
            }
        }
        return result;
    }
    
}


//## 父類 org.springframework.cloud.context.named.NamedContextFactory
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
        implements DisposableBean, ApplicationContextAware {
    // 維護(hù)Ribbon客戶端對(duì)應(yīng)的ApplicationContext上下文
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    // 維護(hù)Ribbon客戶端的@Configuration配置類
    private Map<String, C> configurations = new ConcurrentHashMap<>();

    private ApplicationContext parent;

    private Class<?> defaultConfigType;  // 默認(rèn)配置類為 RibbonClientConfiguration
    private final String propertySourceName;  // 默認(rèn)為 ribbon
    private final String propertyName;  // 默認(rèn)讀取RibbonClient名的屬性為ribbon.client.name

    public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
            String propertyName) {
        this.defaultConfigType = defaultConfigType;
        this.propertySourceName = propertySourceName;
        this.propertyName = propertyName;
    }

    // 如果包含Client上下文直接返回
    // 如果不包含,調(diào)用createContext(name),并放入contexts集合
    protected AnnotationConfigApplicationContext getContext(String name) {
        if (!this.contexts.containsKey(name)) {
            synchronized (this.contexts) {
                if (!this.contexts.containsKey(name)) {
                    this.contexts.put(name, createContext(name));
                }
            }
        }
        return this.contexts.get(name);
    }

    // 創(chuàng)建名為name的RibbonClient的ApplicationContext上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        
        // configurations集合中是否包含當(dāng)前Client相關(guān)配置類,包含即注入到ApplicationContext
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        
        //configurations集合中是否包含default.開頭的通過(guò)@RibbonClients(defaultConfiguration=xxx)配置的默認(rèn)配置類
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        
        // 注冊(cè)PropertyPlaceholderAutoConfiguration、RibbonClientConfiguration
        context.register(PropertyPlaceholderAutoConfiguration.class,
                this.defaultConfigType);
        // 添加 ribbon.client.name=具體RibbonClient name的enviroment配置       
        context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
                this.propertySourceName,
                Collections.<String, Object> singletonMap(this.propertyName, name)));
        
        // 設(shè)置父ApplicationContext,這樣可以使得當(dāng)前創(chuàng)建的子ApplicationContext可以使用父上下文中的Bean
        if (this.parent != null) {
            // Uses Environment from parent as well as beans
            context.setParent(this.parent);
        }
        context.refresh();  //刷新Context
        return context;
    }

    public <T> T getInstance(String name, Class<T> type) {
        AnnotationConfigApplicationContext context = getContext(name);
        if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
                type).length > 0) {
            return context.getBean(type);
        }
        return null;
    }   
}

上面比較重要的就是在創(chuàng)建每個(gè)RibbonClient的ApplicationContext的createContext(name)方法,其中包含了根據(jù)哪個(gè)@Configuration配置類創(chuàng)建Ribbon核心接口的實(shí)現(xiàn)類的邏輯,故需重點(diǎn)分析(Ribbon核心接口講解 參考

那么在createContext(name)方法創(chuàng)建當(dāng)前Ribbon Client相關(guān)的上下文,并注入配置類時(shí),除了默認(rèn)配置類RibbonClientConfiguration是寫死的,其它的配置類,如default全局配置類,針對(duì)某個(gè)Ribbon Client的配置類,又是怎么配置的呢?


Spring Cloud RibbonClient的配置加載,包含全局配置及Client配置

創(chuàng)建RibbonClient對(duì)應(yīng)ApplicationContext,并注冊(cè)所有可用的Configuration配置類

//## org.springframework.cloud.context.named.NamedContextFactory#createContext()
protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    
    // 1、注冊(cè)專門為RibbonClient指定的configuration配置類,@RibbonClient注解
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
            context.register(configuration);
        }
    }
    
    // 2、將為所有RibbonClient的configuration配置類注冊(cè)到ApplicationContext
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    
    // 3、注冊(cè)defaultConfigType,即Spring的默認(rèn)配置類 RibbonClientConfiguration
    context.register(PropertyPlaceholderAutoConfiguration.class,
            this.defaultConfigType);
    context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
            this.propertySourceName,
            Collections.<String, Object> singletonMap(this.propertyName, name)));
    if (this.parent != null) {
        // Uses Environment from parent as well as beans
        context.setParent(this.parent);
    }
    context.refresh();  // 刷新上下文
    return context;
}

根據(jù)如上邏輯可以看出會(huì)從3個(gè)地方將Ribbon相關(guān)的Configuration配置類注冊(cè)到專門為其準(zhǔn)備的ApplicationContext上下文,并根據(jù)配置類創(chuàng)建Ribbon核心接口的實(shí)現(xiàn)類,即達(dá)到配置RibbonClient的目的

  1. 從configurations這個(gè)Map中根據(jù)RibbonClient name獲取專門為其指定的configuration配置類,并注冊(cè)到其對(duì)應(yīng)的ApplicationContext上下文
  2. 從configurations這個(gè)Map中找到 default. 開頭 的配置類,即為所有RibbonClient的默認(rèn)配置,并注冊(cè)到其對(duì)應(yīng)的ApplicationContext上下文
  3. 如果不是開發(fā)者單獨(dú)指定的話,前兩項(xiàng)都是沒有數(shù)據(jù)的,還會(huì)注冊(cè)Spring Cloud的默認(rèn)配置類RibbonClientConfiguration

那么configurations這個(gè)Map里的配置類數(shù)據(jù)是從哪兒來(lái)的呢??下面逐步分析

//## RibbonAutoConfiguration
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();

@Bean
public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
}

首先是在RibbonAutoConfiguration自動(dòng)配置類創(chuàng)建SpringClientFactory是設(shè)置的,這個(gè)configurations集合是@Autowired的Spring容器內(nèi)的RibbonClientSpecification集合,那么RibbonClientSpecification集合是何時(shí)被注冊(cè)的??

//## org.springframework.cloud.netflix.ribbon.RibbonClientConfigurationRegistrar
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        // 1、@RibbonClients注解
        Map<String, Object> attrs = metadata.getAnnotationAttributes(
                RibbonClients.class.getName(), true);
        // 1.1 value是RibbonClient[],遍歷針對(duì)具體的RibbonClient配置的configuration配置類,并注冊(cè)
        if (attrs != null && attrs.containsKey("value")) {
            AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
            for (AnnotationAttributes client : clients) {
                registerClientConfiguration(registry, getClientName(client),
                        client.get("configuration"));
            }
        }
        // 1.2 找到@RibbonClients注解的defaultConfiguration,即默認(rèn)配置
        //     注冊(cè)成以default.Classname.RibbonClientSpecification為名的RibbonClientSpecification
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    attrs.get("defaultConfiguration"));
        }
        
        // 2、@RibbonClient注解
        // 注冊(cè)某個(gè)具體Ribbon Client的configuration配置類
        Map<String, Object> client = metadata.getAnnotationAttributes(
                RibbonClient.class.getName(), true);
        String name = getClientName(client);
        if (name != null) {
            registerClientConfiguration(registry, name, client.get("configuration"));
        }
    }

    private String getClientName(Map<String, Object> client) {
        if (client == null) {
            return null;
        }
        String value = (String) client.get("value");
        if (!StringUtils.hasText(value)) {
            value = (String) client.get("name");
        }
        if (StringUtils.hasText(value)) {
            return value;
        }
        throw new IllegalStateException(
                "Either 'name' or 'value' must be provided in @RibbonClient");
    }

    private void registerClientConfiguration(BeanDefinitionRegistry registry,
            Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }
}

如上可知,configurations配置類集合是根據(jù)@RibbonClient@RibbonClients 注解配置的,分別有 針對(duì)具體某個(gè)RibbonClient的配置default默認(rèn)配置

總結(jié)一下,Ribbon相關(guān)的@Configuration配置類是如何加載的

  1. 在創(chuàng)建完RibbonClient對(duì)應(yīng)的AnnotationConfigApplicationContext后,先從根據(jù)@RibbonClient@RibbonClients 注解加載的configurations集合中找當(dāng)前RibbonClient name對(duì)應(yīng)的配置類,如有,就注冊(cè)到上下文
  2. 再?gòu)腸onfigurations集合中找根據(jù)@RibbonClients注解加載的 default.開頭 的默認(rèn)配置類,如有,就注冊(cè)到上下文
  3. 最后注冊(cè)Spring Cloud默認(rèn)的 RibbonClientConfiguration


上面說(shuō)是如何創(chuàng)建RibbonClient相關(guān)的ApplicationContext上下文及注冊(cè)Ribbon Client相關(guān)的配置類的邏輯,在確定配置類后,其中會(huì)用到Ribbon的IClientConfig相關(guān)的客戶端配置來(lái)加載Ribbon客戶端相關(guān)的配置信息,如超時(shí)配置、具體創(chuàng)建哪個(gè)核心接口的實(shí)現(xiàn)類等,可以從Spring Cloud默認(rèn)注冊(cè)的 RibbonClientConfiguration來(lái)一探究竟


RibbonClientConfiguration配置加載及Ribbon核心接口實(shí)現(xiàn)類創(chuàng)建

//## org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration
@Import({OkHttpRibbonConfiguration.class, RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class})
public class RibbonClientConfiguration {

    @Value("${ribbon.client.name}")
    private String name = "client";

    // TODO: maybe re-instate autowired load balancers: identified by name they could be
    // associated with ribbon clients

    @Autowired
    private PropertiesFactory propertiesFactory;

    @Bean
    @ConditionalOnMissingBean
    public IClientConfig ribbonClientConfig() {
        DefaultClientConfigImpl config = new DefaultClientConfigImpl();
        config.loadProperties(this.name);
        return config;
    }

    @Bean
    @ConditionalOnMissingBean
    public IRule ribbonRule(IClientConfig config) {
        if (this.propertiesFactory.isSet(IRule.class, name)) {
            return this.propertiesFactory.get(IRule.class, config, name);
        }
        ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
        rule.initWithNiwsConfig(config);
        return rule;
    }

上面只截取了一段代碼,給出了Ribbon相關(guān)的 IClientConfig客戶端配置 和 某一個(gè)核心接口IRule實(shí)現(xiàn)類 是如何加載配置并創(chuàng)建的

IClientConfig

IClientConfig就是Ribbon客戶端配置的接口,可以看到先是創(chuàng)建了DefaultClientConfigImpl默認(rèn)實(shí)現(xiàn)類,再config.loadProperties(this.name)加載當(dāng)前Client相關(guān)的配置

//## com.netflix.client.config.DefaultClientConfigImpl#loadProperties()
/**
 * Load properties for a given client. It first loads the default values for all properties,
 * and any properties already defined with Archaius ConfigurationManager.
 */
@Override
public void loadProperties(String restClientName){
    enableDynamicProperties = true;
    setClientName(restClientName);
    
    // 1、使用Netflix Archaius的ConfigurationManager從Spring env中加載“ribbon.配置項(xiàng)”這類默認(rèn)配置
    //   如沒加載到有默認(rèn)靜態(tài)配置
    loadDefaultValues();
    
    // 2、使用Netflix Archaius的ConfigurationManager從Spring env中加載“client名.ribbon.配置項(xiàng)”這類針對(duì)某個(gè)Client的配置信息
    Configuration props = ConfigurationManager.getConfigInstance().subset(restClientName);
    for (Iterator<String> keys = props.getKeys(); keys.hasNext(); ){
        String key = keys.next();
        String prop = key;
        try {
            if (prop.startsWith(getNameSpace())){
                prop = prop.substring(getNameSpace().length() + 1);
            }
            setPropertyInternal(prop, getStringValue(props, key));
        } catch (Exception ex) {
            throw new RuntimeException(String.format("Property %s is invalid", prop));
        }
    }
}

根據(jù)如上注釋,如果你沒有在項(xiàng)目中指定ribbon相關(guān)配置,那么會(huì)使用DefaultClientConfigImpl中的默認(rèn)靜態(tài)配置,如果Spring enviroment中包含“ribbon.配置項(xiàng)”這類針對(duì)所有Client的配置會(huì)被加載進(jìn)來(lái),有“client名.ribbon.配置項(xiàng)”這類針對(duì)某個(gè)Client的配置信息也會(huì)被加載進(jìn)來(lái)

靜態(tài)配置如下:

DefaultClientConfigImpl--static-default-config.jpg


RibbonClient核心接口實(shí)現(xiàn)類配置加載及創(chuàng)建

上面說(shuō)完IClientCOnfig配置項(xiàng)是如何加載的,按道理說(shuō)其中已經(jīng)包含了當(dāng)前RibbonClient使用哪個(gè)核心接口實(shí)現(xiàn)類的配置,但Spring Cloud在此處定義了自己的實(shí)現(xiàn)邏輯

@Autowired
private PropertiesFactory propertiesFactory;

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
    // 查看propertiesFactory是否有關(guān)于當(dāng)前接口的配置,如有就使用,并創(chuàng)建實(shí)例返回
    if (this.propertiesFactory.isSet(IRule.class, name)) {
        return this.propertiesFactory.get(IRule.class, config, name);
    }
    
    // spring cloud 默認(rèn)配置
    ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
    rule.initWithNiwsConfig(config);
    return rule;
}

下面看看PropertiesFactory的邏輯

public class PropertiesFactory {
    @Autowired
    private Environment environment;

    private Map<Class, String> classToProperty = new HashMap<>();

    public PropertiesFactory() {
        classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
        classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
        classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
        classToProperty.put(ServerList.class, "NIWSServerListClassName");
        classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
    }

    // 查看當(dāng)前clazz是否在classToProperty管理的幾個(gè)核心接口之一
    // 如是,查看Spring environment中是否能找到 “clientName.ribbon.核心接口配置項(xiàng)”的配置信息
    public boolean isSet(Class clazz, String name) {
        return StringUtils.hasText(getClassName(clazz, name));
    }

    public String getClassName(Class clazz, String name) {
        if (this.classToProperty.containsKey(clazz)) {
            String classNameProperty = this.classToProperty.get(clazz);
            String className = environment.getProperty(name + "." + NAMESPACE + "." + classNameProperty);
            return className;
        }
        return null;
    }

    // 也是先調(diào)用getClassName()獲取Spring enviroment中配置的核心接口實(shí)現(xiàn)類名
    // 再使用IClientConfig配置信息創(chuàng)建其實(shí)例
    @SuppressWarnings("unchecked")
    public <C> C get(Class<C> clazz, IClientConfig config, String name) {
        String className = getClassName(clazz, name);
        if (StringUtils.hasText(className)) {
            try {
                Class<?> toInstantiate = Class.forName(className);
                return (C) instantiateWithConfig(toInstantiate, config);
            } catch (ClassNotFoundException e) {
                throw new IllegalArgumentException("Unknown class to load "+className+" for class " + clazz + " named " + name);
            }
        }
        return null;
    }
}

故以上面創(chuàng)建IRule接口實(shí)現(xiàn)類的邏輯

  • 先通過(guò)propertiesFactory查看Spring enviroment中是否配置了針對(duì)當(dāng)前Ribbon Client的IRule核心接口實(shí)現(xiàn)類的配置信息,如有,就創(chuàng)建其實(shí)例返回(相關(guān)配置格式: clientName.ribbon.NFLoadBalancerRuleClassName=具體IRule實(shí)現(xiàn)類)
  • 如沒有,那么沒有直接使用Netflix在其DefaultClientConfigImpl中的靜態(tài)配置,而是使用Spring Cloud自定義的默認(rèn)實(shí)現(xiàn)類,拿IRule規(guī)則接口來(lái)說(shuō)是ZoneAvoidanceRule

總結(jié):

首先會(huì)創(chuàng)建RibbonClient的ApplicationContext上下文,并確定使用哪個(gè)Configuration配置類

1、@RibbonClients注冊(cè)的全局默認(rèn)配置類

2、@RibbonClient注冊(cè)的某個(gè)Client配置類

3、Spring Cloud 默認(rèn)的RibbonClientConfiguration配置類

確定配置類后就是加載Client相關(guān)的IClientConfig配置信息,并創(chuàng)建核心接口實(shí)現(xiàn)類

如果沒有自定義全局/客戶端配置類,那么就是使用RibbonClientConfiguration,而其規(guī)則是

對(duì)于超時(shí)等配置(除核心接口實(shí)現(xiàn)類以外):使用Netflix的配置邏輯,通過(guò) ribbon.xxx 作為默認(rèn)配置,以 clientName.ribbon.xxx 作為客戶端定制配置

對(duì)于核心接口實(shí)現(xiàn)類配置:客戶端定制配置仍然使用 clientName.ribbon.xxx,但默認(rèn)配置是Spring Cloud在RibbonClientConfiguration方法中寫死的默認(rèn)實(shí)現(xiàn)類

已經(jīng)知道大概的邏輯了,下面就看看具體如何自定義Client配置、全局配置


如何自定義RibbonClient配置、全局配置

這部分在Spring Cloud官方reference中有說(shuō)明 16.2 Customizing the Ribbon Client

customizing_the_ribbon_client.jpg

大致意思如下:

  • 一部分配置(非核心接口實(shí)現(xiàn)類的配置)可以使用Netflix原生API提供的方式,即使用如 <client>.ribbon.* 的方式配置,具體有哪些配置項(xiàng),可以參考 com.netflix.client.config.CommonClientConfigKey

  • 如果想比較全面的控制RibbonClient并添加一些額外配置,可以使用 @RibbonClient@RibbonClients 注解,并配置一個(gè)配置類,如上的 FooConfiguration

    • @RibbonClient(name = "foo", configuration = FooConfiguration.class) 是針對(duì)名為 foo 的RibbonClient的配置類,也可以使用@RibbonClients({@RibbonClient數(shù)組}) 的形式給某幾個(gè)RibbonClient設(shè)置配置類

    • @RibbonClients( defaultConfiguration = { xxx.class } ) 是針對(duì)所有RIbbonClient的默認(rèn)配置

      • 官方文檔說(shuō) FooConfiguration配置類 必須是@Configuration的,這樣就必須注意,SpringBoot主啟動(dòng)類不能掃描到FooConfiguration,否則針對(duì)某個(gè)RibbonClient的配置就會(huì)變成全局的,原因是在創(chuàng)建每個(gè)RibbonClient時(shí)會(huì)為其創(chuàng)建ApplicationContext上下文,其parent就是主啟動(dòng)類創(chuàng)建的ApplicationContext,子ApplicationContext中可以使用父ApplicationContext中的Bean,且創(chuàng)建Bean時(shí)都使用了@ConditionalOnMissingBean,所以FooConfiguration如果被主啟動(dòng)類的上下文加載,且創(chuàng)建了比如IRule的實(shí)現(xiàn)類,在某個(gè)RIbbonClient創(chuàng)建其子ApplicationContext并@Bean想創(chuàng)建其自定義IRule實(shí)現(xiàn)類時(shí),會(huì)發(fā)現(xiàn)parent ApplicationContext已經(jīng)存在,就不會(huì)創(chuàng)建了,配置就失效了

        但在我的實(shí)驗(yàn)中,即使FooConfiguration不加@Configuration注解也可以加載為RibbonClient的配置,且由于沒有@Configuration了,也不會(huì)被主啟動(dòng)類掃描到

所以主要分成2種配置:

(1)超時(shí)時(shí)間等靜態(tài)配置,使用 ribbon.* 配置所有Client,使用 <client>.ribbon.* 配置某個(gè)Client

(2)使用哪種核心接口實(shí)現(xiàn)類配置,使用@RibbonClients注解做默認(rèn)配置,使用@RibbonClient做針對(duì)Client的配置(注意@Configuration不要被SpringBoot主啟動(dòng)類掃描到的問(wèn)題)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容