事情的原由是這樣的,公司內部要實現基於Zuul網關的灰度路由,在上線時進行灰度測試,故須要配置業務微服務向Eureka註冊的metadata元數據,和自定義Ribbon的負載規則達到只訪問灰度服務的目的。這樣就須要自定義Ribbon的IRule,實現灰度請求只會負載到帶有灰度標籤元數據的業務微服務上,當自定義IRule規則開發好後,問題是如何將這個IRule規則配置給某個Ribbon Client或者全局生效。html
本次使用Spring Cloud Dalston.SR5版本java
在其 官方文檔 中其實已經給出了一些如何針對某個Client 或者 修改默認配置的方式,但沒有說明爲何這樣使用spring
下面將按照這樣的思路分析:apache
當前版本中的Netflix全部自動配置都在spring-cloud-netflix-core-xxx.jar
中,根據其META-INF/spring.factories
中的配置得知,Spring Cloud Ribbon的自動配置類爲 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主要分如下幾類:app
RibbonLoadBalancerClient
,並將springClientFactory注入,方便從中獲取對應的配置及實現類,RibbonLoadBalancerClient
是Spring對LoadBalancerClient
接口的實現類,其execute()
方法提供客戶端負載均衡能力能夠看到默認啓動流程中並無加載RibbonClient的上下文和配置信息,而是在使用時才加載,即懶加載負載均衡
既然是在使用時纔會加載,那麼以Zuul網關爲例,在其RibbonRoutingFilter
中會建立RibbonCommand,其包含了Ribbon的負載均衡ide
//## 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)
,以下:測試
如上執行完畢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的配置類,又是怎麼配置的呢?
//## org.springframework.cloud.context.named.NamedContextFactory#createContext() protected AnnotationConfigApplicationContext createContext(String name) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); // 一、註冊專門爲RibbonClient指定的configuration配置類,@RibbonClient註解 if (this.configurations.containsKey(name)) { for (Class<?> configuration : this.configurations.get(name) .getConfiguration()) { context.register(configuration); } } // 二、將爲全部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); } } } // 三、註冊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的目的
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) { // 一、@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")); } // 二、@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
和 @RibbonClients
註解加載的configurations集合中找當前RibbonClient name對應的配置類,若有,就註冊到上下文@RibbonClients
註解加載的 default.開頭 的默認配置類,若有,就註冊到上下文RibbonClientConfiguration
上面說是如何建立RibbonClient相關的ApplicationContext上下文及註冊Ribbon Client相關的配置類的邏輯,在肯定配置類後,其中會用到Ribbon的IClientConfig
相關的客戶端配置來加載Ribbon客戶端相關的配置信息,如超時配置、具體建立哪一個核心接口的實現類等,能夠從Spring Cloud默認註冊的 RibbonClientConfiguration
來一探究竟
//## 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); // 一、使用Netflix Archaius的ConfigurationManager從Spring env中加載「ribbon.配置項」這類默認配置 // 如沒加載到有默認靜態配置 loadDefaultValues(); // 二、使用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
接口實現類的邏輯
DefaultClientConfigImpl
中的靜態配置,而是使用Spring Cloud自定義的默認實現類,拿IRule
規則接口來講是ZoneAvoidanceRule
總結:
首先會建立RibbonClient的ApplicationContext上下文,並肯定使用哪一個Configuration配置類
一、@RibbonClients註冊的全局默認配置類
二、@RibbonClient註冊的某個Client配置類
三、Spring Cloud 默認的RibbonClientConfiguration配置類
肯定配置類後就是加載Client相關的IClientConfig配置信息,並建立核心接口實現類
若是沒有自定義全局/客戶端配置類,那麼就是使用
RibbonClientConfiguration
,而其規則是對於超時等配置(除核心接口實現類之外):使用Netflix的配置邏輯,經過 ribbon.xxx 做爲默認配置,以 clientName.ribbon.xxx 做爲客戶端定製配置
對於核心接口實現類配置:客戶端定製配置仍然使用 clientName.ribbon.xxx,但默認配置是Spring Cloud在
RibbonClientConfiguration
方法中寫死的默認實現類
已經知道大概的邏輯了,下面就看看具體如何自定義Client配置、全局配置
這部分在Spring Cloud官方reference中有說明 16.2 Customizing the Ribbon Client
大體意思以下:
一部分配置(非核心接口實現類的配置)可使用Netflix原生API提供的方式,即便用如
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主啓動類掃描到的問題)