[] Ribbon SpringCloud how to customize the client configuration and global configuration

Due

It began like this, within the company to achieve gray Zuul gateway routing based on gray-scale test in the line, so you need to configure metadata metadata micro-business services to Eureka registered, and self-supported rule definitions Ribbon reached only purpose of the visit grayscale services. This will need to customize the Ribbon IRule, Grayscale request will only be supported on the micro-business services with gray label metadata, custom IRule after a good rule development, the question is how to configure the rule to a IRule Ribbon Client or global effect.

The use Spring Cloud Dalston.SR5 version

In its official documents in fact we have been given some of the ways for how to configure a Client or modify the default, but did not explain why the use of

The following analysis of these lines:

  • A simple analysis of how to automatically configure when Spring Cloud Ribbon started to understand that is fitted to the Spring Bean
  • Spring Cloud Ribbon Client lazy loading
  • Spring Cloud Ribbon Client configuration is loaded, it contains the global configuration and Client Configuration
  • How to Customize Client configuration, global configuration
  • The official document explained some of the considerations


Spring Cloud Ribbon automatic configuration

The current version of Netflix are all automatically configured spring-cloud-netflix-core-xxx.jarin accordance with their META-INF/spring.factoriesknown configuration, Spring Cloud Ribbon automatic configuration classRibbonAutoConfiguration


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 {

    // 所有针对某个RibbonClient指定的配置
    @Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();
    
    // ribbon是否懒加载的配置文件
    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    // Spring会给每个RibbonClient创建独立的ApplicationContext上下文
    // 并在其上下文中创建RibbonClient对应的Bean:如IClient、ILoadbalancer等
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }

    // Spring创建的带负载均衡功能的Client,会使用SpringClientFactory创建对应的Bean和配置
    @Bean
    @ConditionalOnMissingBean(LoadBalancerClient.class)
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(springClientFactory());
    }

    // 到Spring environment中加载针对某个Client的Ribbon的核心接口实现类
    @Bean
    @ConditionalOnMissingBean
    public PropertiesFactory propertiesFactory() {
        return new PropertiesFactory();
    }
    
    // 如果不是懒加载,启动时就使用RibbonApplicationContextInitializer加载并初始化客户端配置
    @Bean
    @ConditionalOnProperty(value = "ribbon.eager-load.enabled", matchIfMissing = false)
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }

    ......
}

Above RibbonAutoConfigurationBean created mainly divided into the following categories:

  • Create environment for Ribbon Client and access configuration
    • SpringClientFactory : Ribbon Client will each create a separate Spring application context ApplicationContext, and in which load the corresponding core configuration and implementation class interface Ribbon
    • PropertiesFactory : used to get from Spring enviroment environment core configuration for a Ribbon Client interface class, and instantiate
  • Creating RibbonLoadBalancerClientand springClientFactory injection, easy to derive the corresponding configuration and implementation class, RibbonLoadBalancerClientit is the Spring of LoadBalancerClientinterface implementation class, which execute()method provides a client load balancing
  • Lazy loading related
    • RibbonEagerLoadProperties: lazy loading configuration items Properties, you can specify whether lazy loading, lazy loading and which do not Client
    • RibbonApplicationContextInitializer: RibbonClient to load at startup configuration (non-lazy loading) initializer

You can see the default startup process does not load context and configuration information RibbonClient, but loaded only when the use of that lazy loading


Spring Cloud RibbonClient lazy loading

Since it is only loaded when used, the gateway to Zuul, for example, in its RibbonRoutingFiltercreated RibbonCommand, it comprises a Ribbon load balancing

//## RibbonRoutingFilter  Zuul负责路由的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创建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);
        }
    }
}

In execution RibbonRoutingFilter#run()when routing performs forward()the method, is due here in HystrixCommand execution Ribbon load balancing internal calls, so the use of ribbonCommandFactory create RibbonCommand, Ribbon client's lazy loading on in this method, here we look at HttpClientRibbonCommandFactorythe implementation class

//## 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();
        // 通过SpringClientFactory获取IClient接口实例
        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));
    }
}

Create RibbonLoadBalancingHttpClientlogic in SpringClientFactory#getClient(serviceId, RibbonLoadBalancingHttpClient.class), as follows:

  • SpringClientFactory#getInstance(name, clientClass)
    • NamedContextFactory#getInstance(name, type):
      • Corresponding to the ApplicationContext Get Client, if not then call the createContext () created, wherein the default configuration comprises a unified register class RibbonClientConfiguration, or @ RibbonClient, class logic configured @ RibbonClients (defaultConfiguration = xxx) provided
      • Get the type of the ApplicationContext example, if there is no use of reflection to create, and configure by IClientConfig

As finished RibbonClient basically lazy loading is complete, the RibbonClient can continue to obtain the corresponding ApplicationContext in other implementations of the Core interface, these classes are implemented in accordance with the custom default / global / Client configuration created

//## 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代表当前Ribbon客户端,type代表要获取的实例类型,如IClient、IRule
    @Override
    public <C> C getInstance(String name, Class<C> type) {
        // 先从父类NamedContextFactory中直接从客户端对应的ApplicationContext中获取实例
        // 如果没有就根据IClientConfig中的配置找到具体的实现类,并通过反射初始化后放到Client对应的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实例化
    static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
                                        Class<C> clazz, IClientConfig config) {
        C result = null;
        try {
            // 通过以IClientConfig为参数的构造创建clazz类实例
            Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
            result = constructor.newInstance(config);
        } catch (Throwable e) {
            // Ignored
        }
        
        // 如果没创建成功,使用无惨构造
        if (result == null) {
            result = BeanUtils.instantiate(clazz);
            
            // 调用初始化配置方法
            if (result instanceof IClientConfigAware) {
                ((IClientConfigAware) result).initWithNiwsConfig(config);
            }
            
            // 处理自动织入
            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 {
    // 维护Ribbon客户端对应的ApplicationContext上下文
    private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

    // 维护Ribbon客户端的@Configuration配置类
    private Map<String, C> configurations = new ConcurrentHashMap<>();

    private ApplicationContext parent;

    private Class<?> defaultConfigType;  // 默认配置类为 RibbonClientConfiguration
    private final String propertySourceName;  // 默认为 ribbon
    private final String propertyName;  // 默认读取RibbonClient名的属性为ribbon.client.name

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

    // 如果包含Client上下文直接返回
    // 如果不包含,调用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);
    }

    // 创建名为name的RibbonClient的ApplicationContext上下文
    protected AnnotationConfigApplicationContext createContext(String name) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        
        // configurations集合中是否包含当前Client相关配置类,包含即注入到ApplicationContext
        if (this.configurations.containsKey(name)) {
            for (Class<?> configuration : this.configurations.get(name)
                    .getConfiguration()) {
                context.register(configuration);
            }
        }
        
        //configurations集合中是否包含default.开头的通过@RibbonClients(defaultConfiguration=xxx)配置的默认配置类
        for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
            if (entry.getKey().startsWith("default.")) {
                for (Class<?> configuration : entry.getValue().getConfiguration()) {
                    context.register(configuration);
                }
            }
        }
        
        // 注册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)));
        
        // 设置父ApplicationContext,这样可以使得当前创建的子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;
    }   
}

The above is more important in the creation of ApplicationContext each RibbonClient createContext(name)method, which contains the core logic to create a Ribbon interface configuration classes according to which @Configuration implementation class, it takes the focus of analysis (Ribbon Interface to explain the core reference )

Then createContext(name)when you create the current context Ribbon Client related methods, and injection configuration class, in addition to the default configuration class RibbonClientConfigurationis hard-coded, and other configuration class, such as the default global configuration class for a Ribbon Client configuration class, but also how to configure it?


Spring Cloud RibbonClient configuration to load, contain global configuration and Client Configuration

Creating RibbonClient correspondence ApplicationContext, and register all available Configuration configuration class

//## org.springframework.cloud.context.named.NamedContextFactory#createContext()
protected AnnotationConfigApplicationContext createContext(String name) {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
    
    // 1、注册专门为RibbonClient指定的configuration配置类,@RibbonClient注解
    if (this.configurations.containsKey(name)) {
        for (Class<?> configuration : this.configurations.get(name)
                .getConfiguration()) {
            context.register(configuration);
        }
    }
    
    // 2、将为所有RibbonClient的configuration配置类注册到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、注册defaultConfigType,即Spring的默认配置类 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;
}

The above logic can be seen from the register 3 Ribbon place related to the configuration class ApplicationContext Configuration prepared specifically for the context, and create a class that implements interface according Ribbon core configuration class, i.e., to achieve the purpose of configuration RibbonClient

  1. Map configurations obtained from this configuration are arranged specifically for the specified class, and according to its corresponding register RibbonClient name ApplicationContext context
  2. From configurations found this Map of default. At the beginning of class configuration, is the default configuration for all RibbonClient and registered to their respective ApplicationContext context
  3. If not specified, then the individual developer, the first two are no data, but also registered Spring Cloud default configuration classRibbonClientConfiguration

So configurations in the Map class configuration data from where it? ? The following step by step analysis

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

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

The first is to create a class in RibbonAutoConfiguration automatic configuration SpringClientFactoryis set, this set of configurations within the @Autowired the Spring container RibbonClientSpecificationcollection, then the RibbonClientSpecificationcollection is when it was registered? ?

//## 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[],遍历针对具体的RibbonClient配置的configuration配置类,并注册
        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,即默认配置
        //     注册成以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注解
        // 注册某个具体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());
    }
}

As can be seen, is a set of classes Configurations configuration @RibbonClientand @RibbonClientsannotation configuration, respectively, for a particular RibbonClient configuration and the default configuration default

In summary, how Ribbon @Configuration related configuration class is loaded

  1. After creating the corresponding AnnotationConfigApplicationContext RibbonClient, according to start @RibbonClientand @RibbonClientsset the loaded configurations annotations RibbonClient name to find the current configuration corresponding to the class, if any, to the context register
  2. And then find the configurations set according to @RibbonClientsthe annotation loaded default. Beginning default configuration class, if any, to the context register
  3. The last registered Spring Cloud default RibbonClientConfiguration


It says how to create a logical context ApplicationContext and registration Ribbon Client configuration related classes RibbonClient relevant in determining the configuration class, which will use the Ribbon IClientConfigrelated to client configuration to load Ribbon client configuration information, such as time-out configuration, the core of which specifically create the interface implementation class, etc., can be registered by default from Spring Cloud RibbonClientConfigurationtake a closer look


RibbonClientConfiguration load and configure the core Ribbon interface class to create

//## 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;
    }

Only interception of the above piece of code, gives the Ribbon relevant IClientConfigclient configuration and one core interface IRuleto realize how the class is created to load the configuration and

IClientConfig

IClientConfigRibbon is the client configuration interface, you can see first create a DefaultClientConfigImpldefault implementation class, and then config.loadProperties(this.name)load the current Client-related configuration

//## 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.配置项”这类默认配置
    //   如没加载到有默认静态配置
    loadDefaultValues();
    
    // 2、使用Netflix Archaius的ConfigurationManager从Spring env中加载“client名.ribbon.配置项”这类针对某个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));
        }
    }
}

According to the above comments, if you do not specify a ribbon configuration in the project, it will use DefaultClientConfigImplthe default static configuration, if Spring enviroment contains "ribbon. CI" These come in will be loaded for all Client configuration, there is "client name .ribbon. configuration item "type of configuration information for a Client will come loaded

Static configuration is as follows:


RibbonClient core interface implementation class is loaded and configured to create

Having above IClientCOnfighow configuration items are loaded, which stands to reason that already contains a configuration interface which core classes currently RibbonClient use, but Spring Cloud here defines its own implementation of logic

@Autowired
private PropertiesFactory propertiesFactory;

@Bean
@ConditionalOnMissingBean
public IRule ribbonRule(IClientConfig config) {
    // 查看propertiesFactory是否有关于当前接口的配置,如有就使用,并创建实例返回
    if (this.propertiesFactory.isSet(IRule.class, name)) {
        return this.propertiesFactory.get(IRule.class, config, name);
    }
    
    // spring cloud 默认配置
    ZoneAvoidanceRule rule = new ZoneAvoidanceRule();
    rule.initWithNiwsConfig(config);
    return rule;
}

Let's look at PropertiesFactorythe logic

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");
    }

    // 查看当前clazz是否在classToProperty管理的几个核心接口之一
    // 如是,查看Spring environment中是否能找到 “clientName.ribbon.核心接口配置项”的配置信息
    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;
    }

    // 也是先调用getClassName()获取Spring enviroment中配置的核心接口实现类名
    // 再使用IClientConfig配置信息创建其实例
    @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;
    }
}

Therefore, in order to create the above IRulelogical interface class

  • First by propertiesFactory View Spring enviroment is configured in the configuration information for the current IRule core Ribbon Client interface implementation class, if any, would create an instance of return (configuration format: clientName.ribbon.NFLoadBalancerRuleClassName = IRule concrete implementation class)
  • If not, then there is no direct use Netflix in its DefaultClientConfigImplstatic configuration, but the default implementation class Spring Cloud custom, take IRulerule is the interfaceZoneAvoidanceRule

to sum up:

First, create ApplicationContext context RibbonClient and determine which configuration class using Configuration

1, @ RibbonClients registered global default configuration class

2, @ RibbonClient registration of a class Client Configuration

3, Spring Cloud default configuration class RibbonClientConfiguration

确定配置类后就是加载Client相关的IClientConfig配置信息,并创建核心接口实现类

如果没有自定义全局/客户端配置类,那么就是使用RibbonClientConfiguration,而其规则是

对于超时等配置(除核心接口实现类以外):使用Netflix的配置逻辑,通过 ribbon.xxx 作为默认配置,以 clientName.ribbon.xxx 作为客户端定制配置

对于核心接口实现类配置:客户端定制配置仍然使用 clientName.ribbon.xxx,但默认配置是Spring Cloud在RibbonClientConfiguration方法中写死的默认实现类

已经知道大概的逻辑了,下面就看看具体如何自定义Client配置、全局配置


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

这部分在Spring Cloud官方reference中有说明 16.2 Customizing the Ribbon Client

大致意思如下:

  • 一部分配置(非核心接口实现类的配置)可以使用Netflix原生API提供的方式,即使用如 .ribbon. * 的方式配置,具体有哪些配置项,可以参考 com.netflix.client.config.CommonClientConfigKey

  • 如果想比较全面的控制RibbonClient并添加一些额外配置,可以使用 @RibbonClient@RibbonClients 注解,并配置一个配置类,如上的 FooConfiguration

    • @RibbonClient(name = "foo", configuration = FooConfiguration.class) 是针对名为 foo 的RibbonClient的配置类,也可以使用@RibbonClients({@RibbonClient数组}) 的形式给某几个RibbonClient设置配置类

    • @RibbonClients( defaultConfiguration = { xxx.class } ) 是针对所有RIbbonClient的默认配置

      • 官方文档说 FooConfiguration配置类 必须是@Configuration的,这样就必须注意,SpringBoot主启动类不能扫描到FooConfiguration,否则针对某个RibbonClient的配置就会变成全局的,原因是在创建每个RibbonClient时会为其创建ApplicationContext上下文,其parent就是主启动类创建的ApplicationContext,子ApplicationContext中可以使用父ApplicationContext中的Bean,且创建Bean时都使用了@ConditionalOnMissingBean,所以FooConfiguration如果被主启动类的上下文加载,且创建了比如IRule的实现类,在某个RIbbonClient创建其子ApplicationContext并@Bean想创建其自定义IRule实现类时,会发现parent ApplicationContext已经存在,就不会创建了,配置就失效了

        但在我的实验中,即使FooConfiguration不加@Configuration注解也可以加载为RibbonClient的配置,且由于没有@Configuration了,也不会被主启动类扫描到

所以主要分成2种配置:

(1)超时时间等静态配置,使用 ribbon.* 配置所有Client,使用 .ribbon. * 配置某个Client

(2)使用哪种核心接口实现类配置,使用@RibbonClients注解做默认配置,使用@RibbonClient做针对Client的配置(注意@Configuration不要被SpringBoot主启动类扫描到的问题)

Guess you like

Origin www.cnblogs.com/trust-freedom/p/11216280.html