天天看點

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

【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 {

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

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

@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主啟動類掃描到的問題)

原文連結:

https://www.cnblogs.com/trust-freedom/p/11216280.html

作者:Trust_FreeDom - 部落格園

部落格首頁:

http://www.cnblogs.com/trust-freedom/

歡迎轉載,但請保留作者和本文連結,謝謝!