起因

事情的起因是这样的,公司内部要实现基于Zuul网关的灰度路由,在上线时进行灰度测试,故需要配置业务微服务向Eureka注册的metadata元数据,和自定义Ribbon的负载规则达到只访问灰度服务的目的。这样就需要自定义Ribbon的IRule,实现灰度请求只会负载到带有灰度标签元数据的业务微服务上,当自定义IRule规则开发好后,问题是如何将这个IRule规则配置给某个Ribbon Client或者全局生效。

本次使用Spring Cloud Dalston.SR5版本

在其 官方文档 中其实已经给出了一些如何针对某个Client 或者 修改默认配置的方式,但没有说明为什么这样使用

下面将按照这样的思路分析:

  • 简单分析Spring Cloud Ribbon启动时如何自动配置的,以了解其装配到Spring中的Bean
  • Spring Cloud Ribbon Client的懒加载
  • Spring Cloud Ribbon Client的配置加载,包含全局配置及Client配置
  • 如何自定义Client配置、全局配置
  • 解释官方文档中的一些注意事项

Spring Cloud Ribbon自动配置

当前版本中的Netflix所有自动配置都在spring-cloud-netflix-core-xxx.jar中,根据其META-INF/spring.factories中的配置得知,Spring Cloud Ribbon的自动配置类为 RibbonAutoConfiguration

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());
} ......
}

上面RibbonAutoConfiguration创建的Bean主要分以下几类:

  • 为Ribbon Client创建环境及获取配置

    • SpringClientFactory: 会给每个Ribbon Client创建一个独立的Spring应用上下文ApplicationContext,并在其中加载对应的配置及Ribbon核心接口的实现类
    • PropertiesFactory: 用于从Spring enviroment环境中获取针对某个Ribbon Client配置的核心接口实现类,并实例化
  • 创建RibbonLoadBalancerClient,并将springClientFactory注入,方便从中获取对应的配置及实现类,RibbonLoadBalancerClient是Spring对LoadBalancerClient接口的实现类,其execute()方法提供客户端负载均衡能力
  • 懒加载相关
    • RibbonEagerLoadProperties: 懒加载配置项Properties,可以指定是否懒加载,及哪些Client不懒加载
    • RibbonApplicationContextInitializer: 启动时就加载RibbonClient配置(非懒加载)的初始化器

可以看到默认启动流程中并没有加载RibbonClient的上下文和配置信息,而是在使用时才加载,即懒加载

Spring Cloud RibbonClient的懒加载

既然是在使用时才会加载,那么以Zuul网关为例,在其RibbonRoutingFilter中会创建RibbonCommand,其包含了Ribbon的负载均衡

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

在执行RibbonRoutingFilter#run()进行路由时会执行forward()方法,由于此处是在HystrixCommand内部执行Ribbon负载均衡调用,故使用ribbonCommandFactory创建RibbonCommand,Ribbon客户端的懒加载就在这个方法内,这里我们看HttpClientRibbonCommandFactory实现类

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

创建RibbonLoadBalancingHttpClient的逻辑在 SpringClientFactory#getClient(serviceId, RibbonLoadBalancingHttpClient.class),如下:

  • SpringClientFactory#getInstance(name, clientClass)

    • NamedContextFactory#getInstance(name, type):

      • 获取Client对应的ApplicationContext,如没有则调用createContext()创建,其中包含注册统一默认配置类RibbonClientConfiguration,或@RibbonClient、@RibbonClients(defaultConfiguration=xxx) 设置的配置类的逻辑
      • 从ApplicationContext中根据类型获取实例,如没有使用反射创建,并通过IClientConfig配置

如上执行完毕RibbonClient就基本懒加载完成了,就可以到RibbonClient对应的ApplicationContext中继续获取其它核心接口的实现类了,这些实现类都是根据 默认/全局/Client自定义 配置创建的

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

上面比较重要的就是在创建每个RibbonClient的ApplicationContext的createContext(name)方法,其中包含了根据哪个@Configuration配置类创建Ribbon核心接口的实现类的逻辑,故需重点分析(Ribbon核心接口讲解 参考

那么在createContext(name)方法创建当前Ribbon Client相关的上下文,并注入配置类时,除了默认配置类RibbonClientConfiguration是写死的,其它的配置类,如default全局配置类,针对某个Ribbon Client的配置类,又是怎么配置的呢?

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

创建RibbonClient对应ApplicationContext,并注册所有可用的Configuration配置类

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

根据如上逻辑可以看出会从3个地方将Ribbon相关的Configuration配置类注册到专门为其准备的ApplicationContext上下文,并根据配置类创建Ribbon核心接口的实现类,即达到配置RibbonClient的目的

  1. 从configurations这个Map中根据RibbonClient name获取专门为其指定的configuration配置类,并注册到其对应的ApplicationContext上下文
  2. 从configurations这个Map中找到 default. 开头 的配置类,即为所有RibbonClient的默认配置,并注册到其对应的ApplicationContext上下文
  3. 如果不是开发者单独指定的话,前两项都是没有数据的,还会注册Spring Cloud的默认配置类RibbonClientConfiguration

那么configurations这个Map里的配置类数据是从哪儿来的呢??下面逐步分析

//## 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自动配置类创建SpringClientFactory是设置的,这个configurations集合是@Autowired的Spring容器内的RibbonClientSpecification集合,那么RibbonClientSpecification集合是何时被注册的??

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

如上可知,configurations配置类集合是根据@RibbonClient@RibbonClients 注解配置的,分别有 针对具体某个RibbonClient的配置default默认配置

总结一下,Ribbon相关的@Configuration配置类是如何加载的

  1. 在创建完RibbonClient对应的AnnotationConfigApplicationContext后,先从根据@RibbonClient@RibbonClients 注解加载的configurations集合中找当前RibbonClient name对应的配置类,如有,就注册到上下文
  2. 再从configurations集合中找根据@RibbonClients注解加载的 default.开头 的默认配置类,如有,就注册到上下文
  3. 最后注册Spring Cloud默认的 RibbonClientConfiguration

上面说是如何创建RibbonClient相关的ApplicationContext上下文及注册Ribbon Client相关的配置类的逻辑,在确定配置类后,其中会用到Ribbon的IClientConfig相关的客户端配置来加载Ribbon客户端相关的配置信息,如超时配置、具体创建哪个核心接口的实现类等,可以从Spring Cloud默认注册的 RibbonClientConfiguration来一探究竟

RibbonClientConfiguration配置加载及Ribbon核心接口实现类创建

//## 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相关的 IClientConfig客户端配置 和 某一个核心接口IRule实现类 是如何加载配置并创建的

IClientConfig

IClientConfig就是Ribbon客户端配置的接口,可以看到先是创建了DefaultClientConfigImpl默认实现类,再config.loadProperties(this.name)加载当前Client相关的配置

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

根据如上注释,如果你没有在项目中指定ribbon相关配置,那么会使用DefaultClientConfigImpl中的默认静态配置,如果Spring enviroment中包含“ribbon.配置项”这类针对所有Client的配置会被加载进来,有“client名.ribbon.配置项”这类针对某个Client的配置信息也会被加载进来

静态配置如下:

RibbonClient核心接口实现类配置加载及创建

上面说完IClientCOnfig配置项是如何加载的,按道理说其中已经包含了当前RibbonClient使用哪个核心接口实现类的配置,但Spring Cloud在此处定义了自己的实现逻辑

@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;
}

下面看看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");
} // 查看当前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;
}
}

故以上面创建IRule接口实现类的逻辑

  • 先通过propertiesFactory查看Spring enviroment中是否配置了针对当前Ribbon Client的IRule核心接口实现类的配置信息,如有,就创建其实例返回(相关配置格式: clientName.ribbon.NFLoadBalancerRuleClassName=具体IRule实现类)
  • 如没有,那么没有直接使用Netflix在其DefaultClientConfigImpl中的静态配置,而是使用Spring Cloud自定义的默认实现类,拿IRule规则接口来说是ZoneAvoidanceRule

总结:

首先会创建RibbonClient的ApplicationContext上下文,并确定使用哪个Configuration配置类

1、@RibbonClients注册的全局默认配置类

2、@RibbonClient注册的某个Client配置类

3、Spring Cloud 默认的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主启动类扫描到的问题)

【SpringCloud】Ribbon如何自定义客户端配置和全局配置的更多相关文章

  1. uni-app 环境配置,uni.request封装,接口配置,全局配置,接口调用的封装

    1.环境配置 (可参考uni-官网的环境配置) common文件夹下新建config.js let url_config = "" if(process.env.NODE_ENV ...

  2. Ribbon【自定义客户端】

    Ribbon的加载策略是懒加载,即第一次请求的时候才加载对应上下文,正是这个原因,很多时候第一次调用显得很慢,甚至会超时,所以,可以通过指定ribbon具体服务名称来开启饿加载,即在工程启动的时候,加 ...

  3. Keepalived的全局配置

    Keepalived的全局配置 默认配置文件如下: ! Configuration File for keepalived global_defs { notification_email { aca ...

  4. 客户端实现负载均衡:springCloud Ribbon的使用

    Netfilx发布的负载均衡器,是一个基于http.tcp的客户端负载均衡工具,具有控制http.tcp客户端的行为,为ribbon配置服务提供者的地址后,ribbon就 可以经过springClou ...

  5. spring-cloud构架微服务(1)-全局配置

    使用spring-cloud是基于熟悉springboot基础上进行的.本篇介绍全局配置,spring-boot版本就以1.4.0来做吧.项目地址: https://git.oschina.net/b ...

  6. SpringCloud系列九:SpringCloudConfig 基础配置(SpringCloudConfig 的基本概念、配置 SpringCloudConfig 服务端、抓取配置文件信息、客户端使用 SpringCloudConfig 进行配置、单仓库目录匹配、应用仓库自动选择、仓库匹配模式)

    1.概念:SpringCloudConfig 基础配置 2.具体内容 通过名词就可以发现,SpringCloudConfig 核心作用一定就在于进行配置文件的管理上.也就是说为了更好的进行所有微服务的 ...

  7. SpringCloud之Feign声明式调用原理及配置

    1 什么是Feign Feign是一种声明式.模板化的HTTP客户端(仅在Application Client中使用).声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求. ...

  8. springcloud Ribbon学习笔记二

    之前介绍了如何搭建eureka服务并开发了一个用户服务成功注册到了eureka中,接下来介绍如何通过ribbon来从eureka中获取用户服务: springcloud ribbon提供客户端的负载均 ...

  9. 微信小程序开发——全局配置详细介绍

    本文针对官方文档未说明清楚的进行详细探索研究,官方文档详见:全局配置 . pages: 删除页面,需要先删除app.json中pages对应页面的路径,再去删除页面相关的所有文件,不然会出现页面文件混 ...

随机推荐

  1. Android零基础入门第72节:SwipeRefreshLayout下拉刷新

    在实际开发中,经常都会遇到下拉刷新.上拉加载更多的情形,这一期就一起来学习Android系统的SwipeRefreshLayout下拉刷新组件. 一.SwipeRefreshLayout简介 Swip ...

  2. WordPress中.user.ini 权限问题

    安装完WordPress在给予权限或者删除的时候可能会收到下面的提示. [root@localhost default]# chmod -R 755 /home/wwwroot chmod: 更改&q ...

  3. c# 叫号小程序

    写个叫号的小demo 长相如下 代码如下 using System; using System.Collections.Generic; using System.ComponentModel; us ...

  4. 三个臭皮匠,顶上一个诸葛亮——在Google Ideathon上Design Thinking分享

    4月26日很荣幸的被邀请参加Google Ideathon做Design Thinking的分享. 这次主要分享了Design Thinking的基本方法流程,以及在真实项目的运用.现在整理一下当时选 ...

  5. 通过OSG实现对模型的日照模拟

    目录 1. 加载模型 2. 光照 1) 环境反射 2) 漫反射 3) 日照方向 (1) 太阳高度角和太阳方位角 (2) 计算过程 4) 改进实现 3. 阴影 4. 太阳高度角与太阳方位角的计算 1) ...

  6. python读取json文件并解析

    # -*- coding: utf-8 -*- import os import json import sys reload(sys) sys.setdefaultencoding('utf-8') ...

  7. Laravel中我们登录服务器通过 Tinker 手动创建后台管理用户

    Laravel中我们可以登录到服务器通过 Tinker 手动创建后台用户 laravel中tinker命令的作用是用来调试laravel,可以打印变量或对象信息,显示函数代码,对数据库写入和查询数据. ...

  8. 如何开发优质的 Flutter App:应用架构的搭建

    各位读者朋友们,好久不见了! 最近博主一直在忙于工作以及写<Flutter入门与应用实战>的书,所以没有时间打理博客.今天来给大家分享一个博主在GitChat上发起的一场Chat. 下面是 ...

  9. sed命令和find命令的结合的使用

    linux中查找当前目录及其子目录下的所有test.txt文件,并将文件中的oldboy替换成oldgirl 首先查找出当前目录及其子目录下的所有的test.txt文件 [root@zxl zxl]# ...

  10. spring源码解析之IOC容器(四)——属性注入

    上一篇跟踪了bean的创建过程,接下来,我们继续跟踪bean的属性填充的过程.先回到doCreateBean方法,代码如下: protected Object doCreateBean(final S ...