转载

从源码看Ribbon为何能自动发现Nacos中的服务

最近看Ribbon源码的时候发现,虽然Ribbon中指定了ServerList的Bean,但是实际使用中却是调用的Nacos的服务,并没有用到自身的ServerList实现(ConfigurationBasedServerList),于是继续深入看了下,找到原因记录在此

本文中SpringBoot版本号为2.2.5.RELEASE,SpringCloud版本号为Hoxton.SR3,SpringCloudAlibaba版本号位2.2.0.RELEASE。POM文件关键部分如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>2.2.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

源码分析

说到底,关键是找到Ribbon中ServerList实现类是如何进行Bean创建的

先上一张大致的流程图

从源码看Ribbon为何能自动发现Nacos中的服务

关键配置类

RibbonNacosAutoConfiguration

该类为Nacos的自动配置类之一,源码如下

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnBean(SpringClientFactory.class)
@ConditionalOnRibbonNacos
@ConditionalOnNacosDiscoveryEnabled
@AutoConfigureAfter(RibbonAutoConfiguration.class)
@RibbonClients(defaultConfiguration = NacosRibbonClientConfiguration.class)
public class RibbonNacosAutoConfiguration {

}

该类中首先配置了@RibbonClients注解,并指明了默认配置类,接下来看@RibbonClients注解的内容:

@Configuration(proxyBeanMethods = false)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {
    RibbonClient[] value() default {};
    Class<?>[] defaultConfiguration() default {};
}

@RibbonClients注解使用@Import导入了RibbonClientConfigurationRegistrar类,由于@Import的执行优先级会高于@Bean,所以RibbonClientConfigurationRegistrar中的registerBeanDefinitions方法会优先执行。现在看看其内部都做了些什么逻辑:

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
                                    BeanDefinitionRegistry registry) {
    // ...
    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"));
    }
    // ...
}


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

由代码可见,registerBeanDefinitions会将@RibbonClients注解的defaultConfiguration配置类名加上 default. 前缀作为name属性,然后调用RibbonClientSpecification类的构造方法构造一个name属性为 default.com.alibaba.cloud.nacos.ribbon.NacosRibbonClientConfiguration 的configuration配置类到Spring容器中,该类中包含一个Nacos自己的 ServerList 实现

RibbonClientConfiguration

该配置类中包含了Ribbon客户端中的基本配置,这里涉及到两个重要的Bean创建:

@Bean
@ConditionalOnMissingBean
public ServerList<Server> ribbonServerList(IClientConfig config) {
    if (this.propertiesFactory.isSet(ServerList.class, name)) {
        return this.propertiesFactory.get(ServerList.class, config, name);
    }
    ConfigurationBasedServerList serverList = new ConfigurationBasedServerList();
    serverList.initWithNiwsConfig(config);
    return serverList;
}

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config,
                                        ServerList<Server> serverList, ServerListFilter<Server> serverListFilter,
                                        IRule rule, IPing ping, ServerListUpdater serverListUpdater) {
    if (this.propertiesFactory.isSet(ILoadBalancer.class, name)) {
        return this.propertiesFactory.get(ILoadBalancer.class, config, name);
    }
    return new ZoneAwareLoadBalancer<>(config, rule, ping, serverList,
                                       serverListFilter, serverListUpdater);
}

这里 ribbonLoadBalancerServerList 作为参数进行创建,虽然同类中也指定了ServerList的Bean方法,但是实际却没有调用,这也是最开始困惑我的地方,那么ILoadBalancer里的ServerList是哪里来的呢?

RibbonAutoConfiguration

该配置类中实例化了一个SpringClientFactory的bean对象,并注入了一个RibbonClientSpecification配置类集合:

@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();

@Bean
public SpringClientFactory springClientFactory() {
    SpringClientFactory factory = new SpringClientFactory();
    factory.setConfigurations(this.configurations);
    return factory;
}

我们在小学二年级都知道,Spring注入List集合对象默认会将容器中所有该类型的对象自动注入,也就是说这里会将前面的registerBeanDefinitions中创建的Bean进行注入

在SpringClientFactory构造方法中,指定了defaultConfigType为RibbonClientConfiguration:

public SpringClientFactory() {
    super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}

关键的地方来了,当Ribbon在实际使用中需要调用SpringClientFactory.getLoadBalancer方法获取负载均衡器时,由于此时容器中是没有AnnotationConfigApplicationContext上下文对象的,这时容器会调用 createContext 方法手动创建:

protected AnnotationConfigApplicationContext createContext(String name) {
    // ...
    for (Map.Entry<String, C> entry : this.configurations.entrySet()) {
        if (entry.getKey().startsWith("default.")) {
            for (Class<?> configuration : entry.getValue().getConfiguration()) {
                context.register(configuration);
            }
        }
    }
    context.register(PropertyPlaceholderAutoConfiguration.class,
                     this.defaultConfigType);
    // ...
}

这段代码表明了创建上下文对象的优先级,即 Specification配置优先于defaultConfigType配置

由于之前在Nacos中已经将NacosRibbonClientConfiguration配置类作为参数创建了RibbonClientSpecification配置,并且name属性也符合 entry.getKey().startsWith("default.") ,因此for循环中的context.register方法会优先调用,当跳出for循环再次调用register方法时,由于之前的Specification中已经有了ribbonServerList,并且Ribbon自带的ribbonServerList使用了@ConditionalOnMissingBean注解,因此不会对其进行创建,到这里,就解释了Ribbon是如何自动发现Nacos服务的问题

总结

其实熟悉Ribbon的人都应该知道可以针对不同的客户端做不同的配置,也就是@RibbonClients与@RibbonClient注解的使用,这里从源码上看了下里面的实现逻辑

总的一句话来讲,@RibbonClients指定的配置会覆盖Ribbon已有的配置

原文  http://www.jptangchina.com/2020/04/09/cong-yuan-ma-kan-ribbon-wei-he-neng-zi-dong-fa-xian-nacos-zhong-de-fu-wu/
正文到此结束
Loading...