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


起因

事情的起因是這樣的,公司內部要實現基於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主啟動類掃描到的問題)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM