SpringCloud之Ribbon组件配置的读取
spring:
application:
name: eshop-microservice-gateway-app
server:
port: 8080
eureka:
client:
defautlZone: http://127.0.0.1:8761/eureka,http://127.0.0.1:8762/eureka
#ribbon的全局配置,对所有ribbon客户端都生效
ribbon:
MaxAutoRetries: 2
eshop-microservice-user:
ribbon:
ReadTimeout: 4000
zuul:
ignored-services: '*'
retryable: true
stripPrefix: false
routes:
userservice:
serviceId: eshop-microservice-user
path: /api/v1.0/user/**
stripPrefix: false
testservice:
url: http://www.baidu.com/
path: /test/**
demoservice: /aaaa/**
配置文件中Ribbon的配置如下
eshop-microservice-user:
ribbon:
ReadTimeout: 4000
在springcloud里是怎么读取的,我们先看ribbon的调用入口:
public HttpClientRibbonCommand create(final RibbonCommandContext context) {
FallbackProvider zuulFallbackProvider = getFallbackProvider(
context.getServiceId());
final String serviceId = context.getServiceId();
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));
}
这是Zuul组件调用Ribbon的入口,由clientFactory创建RibbonLoadBalancingHttpClient,我们进而去看this.clientFactory.getClient方法,通过步步查看可以看出在创建RibbonLoadBalancingHttpClient的时候创建了Spring容器,然后从容器获取的,创建容器的逻辑如下:
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
if (entry.getKey().startsWith("default.")) {
for (Class<?> configuration : entry.getValue().getConfiguration()) {
context.register(configuration);
}
}
}
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);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
这里面我们主要关心的是红色的代码,里面是在properties集合里添加了一个配置,这个配置我们知道name就是前面的serviceId,这里也就是eshop-microservice-user,但是propertyName具体是啥呢,我们接下来看:
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
}
这是SpringClientFactory的无参构造函数,通过这里我们可以知道propertyName=ribbon.client.name,所以properties集合添加的配置是:ribbon.client.name=eshop-microservice-user,而这到底用在哪里呢?我们可以先看看新创建的这个spring容器到底注入了哪些Configuration,在前面的createContext方法我们看到context.register(PropertyPlaceholderAutoConfiguration.class,this.defaultConfigType);这样一句代码,defaultConfigType就是RibbonClientConfiguration,然后我们看看RibbonClientConfiguration的几个关键地方
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
@RibbonClientName
private String name = "client";
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
return config;
}
上面的name属性有RibbonClientName注解,如下
@Value("${ribbon.client.name}")
public @interface RibbonClientName {
}
这样name的值在容器刷新后被ribbon.client.name替换成eshop-microservice-user,然后注入IClientConfig,ribbonClientConfig里面的逻辑就是初始化ribbon的配置并且加载ribbon的自定义配置。
然后我们看加载配置主要的方法loadProperties:
public void loadProperties(String restClientName){
enableDynamicProperties = true;
setClientName(restClientName);
//这里加载全局的ribbon配置
loadDefaultValues();
//这里加载对应客户端的配置
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));
}
}
}
在DefaultClientConfigImpl这个实现内部有个地方需要注意:
protected Object getProperty(String key) {
if (enableDynamicProperties) {
String dynamicValue = null;
DynamicStringProperty dynamicProperty = dynamicProperties.get(key);
if (dynamicProperty != null) {
dynamicValue = dynamicProperty.get();
}
if (dynamicValue == null) {
dynamicValue = DynamicProperty.getInstance(getConfigKey(key)).getString();
if (dynamicValue == null) {
dynamicValue = DynamicProperty.getInstance(getDefaultPropName(key)).getString();
}
}
if (dynamicValue != null) {
return dynamicValue;
}
}
return properties.get(key);
}
在DefaultClientConfigImpl的内部有properties和dynamicProperties来存储配置信息,所以读取配置时会先从dynamicProperties中读取,如果没读取到则从properties读,而用户自定义的配置在properties和dynamicProperties中都会存储,所以就有
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT); config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT); config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
这三个重置默认值的代码并不影响用户自定义的配置。

浙公网安备 33010602011771号