最近看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已有的配置