最近看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创建的
先上一张大致的流程图
该类为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
实现
该配置类中包含了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); }
这里 ribbonLoadBalancer
将 ServerList
作为参数进行创建,虽然同类中也指定了ServerList的Bean方法,但是实际却没有调用,这也是最开始困惑我的地方,那么ILoadBalancer里的ServerList是哪里来的呢?
该配置类中实例化了一个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已有的配置