[] 리본 SpringCloud 어떻게 클라이언트 구성 및 글로벌 구성을 사용자 정의하기

유래

이 라인의 그레이 스케일 테스트를 기반으로 회색 Zuul 게이트웨이 라우팅을 달성하기 위해 회사 내에서, 다음과 같이 시작했다, 그래서 당신은 유레카에 메타 데이터 메타 데이터 마이크로 비즈니스 서비스를 구성 등록 및 자체 지원 규칙 정의는 리본 만에 도달 할 필요가 방문 그레이 스케일 서비스의 목적. 이 리본 아이 룰을 사용자 정의해야합니다, 그레이 스케일 요청은 좋은 규칙 개발 후 회색 라벨 메타 데이터, 사용자 정의 아이 룰과 마이크로 비즈니스 서비스에 지원 될 것입니다, 문제는 아이 룰에 규칙을 구성하는 방법입니다 리본 클라이언트 또는 글로벌 효과.

사용의 봄 클라우드 Dalston.SR5 버전

그것에서 공식 문서 사실 우리는 클라이언트를 구성하거나 기본값을 수정하는 방법에 대한 몇 가지 방법을 주어졌다, 그러나의 왜 사용을 설명하지 않았다

이 라인의 다음과 같은 분석 :

  • 봄 클라우드 리본이 그것을 이해하기 시작했을 때 자동으로 구성하는 방법의 간단한 분석은 봄 콩에 장착되어있다
  • 봄 클라우드 리본 클라이언트 지연로드
  • 봄 클라우드 리본 클라이언트 구성이로드, 그것은 세계적인 구성 및 클라이언트 구성을 포함
  • 글로벌 구성 클라이언트 구성을 사용자 지정하는 방법
  • 공식 문서는 몇 가지 고려 사항을 설명


봄 클라우드 리본 자동 구성

넷플릭스의 현재 버전은 모두 자동으로 구성됩니다 spring-cloud-netflix-core-xxx.jar자신의에 따라 META-INF/spring.factories알려진 구성, 봄 클라우드 리본 자동 구성 클래스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콩 주로 다음과 같은 범주로 나누어 만든 :

  • 리본 클라이언트 액세스 구성을위한 환경 조성
    • SpringClientFactory는 : 리본 클라이언트는 각각 별도의 Spring 애플리케이션 컨텍스트의 ApplicationContext를 생성하고있는 해당 핵심 구성 및 구현 클래스 인터페이스 리본을로드합니다
    • PropertiesFactory : 리본 클라이언트 인터페이스 클래스에 봄 환경 환경의 핵심 구성에서 얻고, 인스턴스화하는 데 사용
  • 생성 RibbonLoadBalancerClient하고 springClientFactory 주입, 쉽게 대응하는 구성 및 구현 클래스를 유도하는데, RibbonLoadBalancerClient그 스프링의 LoadBalancerClient인터페이스 구현 클래스, execute()메소드가 클라이언트로드 밸런싱을 제공하고는
  • 게으른 로딩 관련
    • RibbonEagerLoadProperties : 게으른 로딩 구성 항목 속성, 당신은 게으른 로딩, 게으른 로딩하고있는 클라이언트를하지 여부를 지정할 수 있습니다
    • RibbonApplicationContextInitializer는 : RibbonClient는 시작 구성 (비 지연로드) 초기화에로드

당신은 컨텍스트 및 구성 정보 RibbonClient를로드하지 않은 기본 시작 프로세스를 볼 수 있지만로드 할 수있을 때만이 지연로드의 사용


봄 클라우드 RibbonClient 지연로드

사용될 때에 만로드되므로 Zuul 게이트웨이는 예를 들어, 그것의 RibbonRoutingFilter생성 RibbonCommand, 이는 리본 부하 분산을 포함

//## 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 ribbonCommandFactory의 사용이 방법에 리본 클라이언트의 게으른 로딩을 RibbonCommand을 만들 수 있도록, 내부 통화의 균형을 실행 리본로드, 우리가 살펴 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 (이름, clientClass)
    • NamedContextFactory #의 getInstance (이름, 유형) :
      • 다음 제공된 기본 구성은 통합 레지스터 클래스 RibbonClientConfiguration를 포함하는 생성 CreateContext에 (), 또는 RibbonClient @ RibbonClients @ 구성된 클래스 로직 (defaultConfiguration = XXX)를 호출 할 경우의 ApplicationContext에 대응하는, 클라이언트 얻기
      • 만들 수있는 반사 아무 소용이없는 경우, ApplicationContext를 예제의 형태를 취득하고, IClientConfig에 의해 구성

완료 RibbonClient 기본적으로 게으른로드가 완료 때, RibbonClient가 핵심 인터페이스의 다른 구현에서 해당의 ApplicationContext를 얻기 위해 계속해서,이 클래스에 따라 구현되는 사용자 지정 기본 / 글로벌 / 클라이언트 생성 된 형상

//## 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;
    }   
}

위에서의 ApplicationContext 각 RibbonClient의 생성에 중요하다 createContext(name)@Configuration 구현 클래스가, 그 분석의 포커스를 필요에 따라되는 리본 인터페이스 구성 클래스를 생성하기 위해 상기 코어 로직을 포함하는 방법 (리본 인터페이스 코어 설명하기 위해 참조 )

그리고 createContext(name)현재 상황에 맞는 리본 클라이언트 관련 방법 및 주입 구성 클래스를 만들 때, 기본 구성 클래스에 추가로 RibbonClientConfiguration이러한 리본 클라이언트 구성 클래스에 대한 기본 전역 구성 클래스로 하드 코드 및 기타 구성 클래스는, 그러나 또한 방법을 구성하는 방법 그것은?


봄 클라우드 RibbonClient로드 구성은 글로벌 구성 및 클라이언트 구성을 포함

RibbonClient 대응의 ApplicationContext를 생성, 사용 가능한 모든 구성 구성 클래스를 등록

//## 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;
}

상기 로직의 ApplicationContext 구성 컨텍스트를 위해 특별히 제조 된 구성 클래스 관련 레지스터 3 리본 장소에서 본, 및 인터페이스에 따라 리본 코어 구성 클래스, 즉 구현 클래스를 만들 구성 RibbonClient의 목적을 달성 할 수있다

  1. 이 구성에 의한 맵 구성은 지정된 클래스 특별히 배치되고, 해당 레지스터 RibbonClient 이름 ApplicationContext의 콘텍스트에있어서되고
  2. 구성에서이지도 발견 기본. 처음에는 클래스 구성의 모든 RibbonClient의 기본 구성이며, 각각의 ApplicationContext의 컨텍스트에 등록
  3. 지정되지 않은 경우, 처음 두 데이터뿐만 아니라 등록 봄 클라우드의 기본 구성 클래스는 개인 개발자 없습니다RibbonClientConfiguration

그래서 구성은 그것에서 구성 데이터에서이 클래스를지도? ? 분석 단계의 다음 단계

//## 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설정 스프링 용기 내의 상기 @Autowired 구성의 세트를 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());
    }
}

알 수있는 바와 같이, 클래스 구성 구성의 집합입니다 @RibbonClient@RibbonClients주석 구성은, 각각 특정 RibbonClient의 구성기본 구성 기본

요약하면, 리본 @Configuration 관련 방법 구성 클래스는로드입니다

  1. 해당 AnnotationConfigApplicationContext RibbonClient를 생성 한 후, 시작에있어서 @RibbonClient@RibbonClients로드 구성을 설정하는 컨텍스트 레지스터 (있는 경우), 클래스에 대응하는 현재의 설정을 찾기 위해 RibbonClient 이름 애노테이션
  2. 그리고 다음에 따라 설정 구성을 찾아 @RibbonClients주석로드 기본. 시작 컨텍스트 레지스터에있는 경우, 기본 구성 클래스를
  3. 마지막으로 등록 된 봄 클라우드의 기본 RibbonClientConfiguration


그것은 리본 사용 관련 클래스 구성 클래스를 결정 관련 RibbonClient 논리적 맥락의 ApplicationContext 및 등록 리본 클라이언트 구성, 작성하는 방법을 말한다 IClientConfig같은 시간 초과로 리본 클라이언트 구성 정보를로드하기 위해 클라이언트 구성과 관련된를 구성, 특히 인터페이스 구현 클래스를 만들 수있는 핵심은, 등, 봄 클라우드에서 기본적으로 등록 할 수있는 것은 RibbonClientConfiguration더 자세히 살펴


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

위의 코드 조각 만 차단은, 리본 관련주는 IClientConfig클라이언트 구성 및 하나의 코어 인터페이스를 IRule클래스가 구성을로드하기 위해 생성하는 방법을 실현하고,

IClientConfig

IClientConfig리본 클라이언트 구성 인터페이스를 먼저 참조하십시오 만들 수 있습니다 DefaultClientConfigImpl기본 구현 클래스를 한 다음 config.loadProperties(this.name)현재 클라이언트 관련 구성을로드

//## 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));
        }
    }
}

프로젝트에 리본 구성을 지정하지 않은 경우 위의 의견에 따르면, 그것은 사용 DefaultClientConfigImpl봄 환경은 "리본. CI는"이 모든 클라이언트 구성을 위해로드됩니다 와서이 포함 된 경우, 기본 정적 구성을, "클라이언트가 .ribbon의 이름을 지정합니다. 구성 정보의 구성 항목 "유형을 클라이언트가로드 올 것이다위한

다음 정적 구성은 :


RibbonClient 핵심 인터페이스 구현 클래스는로드 생성하도록 구성되어

위 갖는 IClientCOnfig구성 항목을로드하는 방법, 이는 이미 현재 핵심 클래스 RibbonClient 사용하지만, 봄 클라우드 여기에 논리 자체 구현을 정의하는 구성 인터페이스를 포함 이유에 서

@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 보기 봄 환경이있는 경우, 반환의 인스턴스를 만들 것이다, 현재의 아이 룰 코어 리본 클라이언트 인터페이스 구현 클래스에 대한 구성 정보를 구성 (구성 형식 : clientName.ribbon.NFLoadBalancerRuleClassName = 아이 룰 구체적인 구현 클래스)
  • 그렇지 않다면, 거기에 그에서 직접 사용 넷플릭스없는 DefaultClientConfigImpl정적 구성,하지만, 받아 기본 구현 클래스 봄 클라우드 사용자 정의 IRule규칙은 인터페이스ZoneAvoidanceRule

요약 :

첫째, ApplicationContext의 컨텍스트 RibbonClient를 생성 및 구성을 사용하는 구성 클래스를 결정

1, RibbonClients 등록 @ 전역 기본 구성 클래스

2, 클래스 클라이언트 구성의 @의 RibbonClient 등록

3, 봄 클라우드의 기본 구성 클래스 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主启动类扫描到的问题)

추천

출처www.cnblogs.com/trust-freedom/p/11216280.html