起因
事情的起因是這樣的,公司內部要實現基於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配置
- NamedContextFactory#getInstance(name, type):
如上執行完畢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的目的
- 從configurations這個Map中根據RibbonClient name獲取專門為其指定的configuration配置類,並註冊到其對應的ApplicationContext上下文
- 從configurations這個Map中找到 default. 開頭 的配置類,即為所有RibbonClient的預設配置,並註冊到其對應的ApplicationContext上下文
- 如果不是開發者單獨指定的話,前兩項都是沒有資料的,還會註冊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配置類是如何載入的
- 在建立完RibbonClient對應的AnnotationConfigApplicationContext後,先從根據
@RibbonClient
和@RibbonClients
註解載入的configurations集合中找當前RibbonClient name對應的配置類,如有,就註冊到上下文 - 再從configurations集合中找根據
@RibbonClients
註解載入的 default.開頭 的預設配置類,如有,就註冊到上下文 - 最後註冊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,使用
(2)使用哪種核心介面實現類配置,使用@RibbonClients註解做預設配置,使用@RibbonClient做針對Client的配置(注意@Configuration不要被SpringBoot主啟動類掃描到的問題)