目录
RibbonClientConfigurationRegistrar
在微服务系统中调用微服务A和微服务B,因为业务、逻辑、性能等各种不同,我们不能对两个微服务系统使用同一套Ribbon参数配置,Spring Cloud Ribbon也支持我们针对不同服务进行定制化配置,例如超时时间、负载均衡策略等等。配置分为三种,默认/全局/各Client自定义配置,本次从源码角度来分析Ribbon三种配置。
入口自然是spring.factories中的自动配置类,只列举出了涉及到的bean org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
@Configuration
@Conditional(RibbonAutoConfiguration.RibbonClassesConditions.class)
@RibbonClients
@AutoConfigureAfter(
name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
@AutoConfigureBefore({ LoadBalancerAutoConfiguration.class,
AsyncLoadBalancerAutoConfiguration.class })
@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
ServerIntrospectorProperties.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
havingValue = "true", matchIfMissing = true)
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
@ConditionalOnMissingBean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
@Bean
@ConditionalOnMissingBean
public PropertiesFactory propertiesFactory() {
return new PropertiesFactory();
}
}
上边代码中涉及到的Bean有四个
RibbonClientSpecification 针对某个RibbonClient自定义的配置类,name为微服务名,configuration是配置类Class,怎么来的先忽略
SpringClientFactory 继承自NamedContextFactory,RibbonClient的配置工厂类,Spring为每个RibbonClient都会创建独立的子容器,在子容器中创建Ribbon所需要的Bean(IRule、ILoadBalancer等)
RibbonLoadBalancerClient 调用SpringClientFactory获取ILoadBalancer进行负载均衡选择,随后发送请求
PropertiesFactory Spring提供的针对某个RibbonClient的自定义实现类,也是定制化配置的一种方式
NamedContextFactory
这个类允许创建一系列的子容器,在子容器中创建不同的bean,简单说就是每个name下有独立的容器,有默认的配置类,也有所有name公用的全局配置类,也可以为每个name自定义配置类(@Configuration),每个子容器中加载 默认、全局、以及属于自己的配置类中的bean定义
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
// 当前NamedContextFactory名
private final String propertySourceName;
private final String propertyName;
// key是name,ribbon中就是服务名,value是子容器
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
// key是name,ribbon中就是服务名,value是该服务自定义的配置类
private Map<String, C> configurations = new ConcurrentHashMap<>();
// 父容器,就是当前应用的spring容器
private ApplicationContext parent;
// 默认的配置类
private Class<?> defaultConfigType;
public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName,
String propertyName) {
this.defaultConfigType = defaultConfigType;// 默认配置
this.propertySourceName = propertySourceName;
this.propertyName = propertyName;
}
// 每个子容器的自定义配置
public interface Specification {
String getName();
Class<?>[] getConfiguration();
}
}
获取Bean的方法如下,从map中根据name获取子容器,没有则创建,再从容器中getBean
public <T> T getInstance(String name, Class<T> type) {
// 根据name先获取容器
AnnotationConfigApplicationContext context = getContext(name);
try {
return context.getBean(type);
}
catch (NoSuchBeanDefinitionException e) {
// ignore
}
return null;
}
// 不存在则创建
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);
}
protected AnnotationConfigApplicationContext createContext(String name) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 如果有为每个name自定义的配置类。将其注册到容器
if (this.configurations.containsKey(name)) {
for (Class<?> configuration : this.configurations.get(name)
.getConfiguration()) {
context.register(configuration);
}
}
// 如果有全局的配置类,以default开头的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);
context.getEnvironment().getPropertySources().addFirst(new MapPropertySource(
this.propertySourceName,
Collections.<String, Object>singletonMap(this.propertyName, name)));
// 关联父子容器,进行refresh
if (this.parent != null) {
// Uses Environment from parent as well as beans
context.setParent(this.parent);
// jdk11 issue
// https://github.com/spring-cloud/spring-cloud-netflix/issues/3101
context.setClassLoader(this.parent.getClassLoader());
}
context.setDisplayName(generateDisplayName(name));
context.refresh();
return context;
}
SpringClientFactory
继承自NamedContextFactory,RibbonClientConfiguration是所有子容器也就是所有RibbonClient的默认配置类,RibbonClientSpecification则就是RibbonClient的自定义配置封装类
RibbonClientConfiguration
所有RibbonClient的默认配置类,其中有两种配置方式
一种普通参数就是配置文件ribbon.connectTimeout=1000
第二种核心组件配置,先使用PropertiesFactory(RibbonAutoConfiguration中的)获取,未获取到则给出默认实现
@SuppressWarnings("deprecation")
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {
// ribbon的普通参数,超时时间
public static final int DEFAULT_CONNECT_TIMEOUT = 1000;
public static final int DEFAULT_READ_TIMEOUT = 1000;
@RibbonClientName
private String name = "client";
@Autowired
private PropertiesFactory propertiesFactory;
@Autowired
private Environment environment;
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);
// 超时时间通过application.properties中的ribbon.connectTimeout来设置
config.set(CommonClientConfigKey.ConnectTimeout, getProperty(
CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT));
config.set(CommonClientConfigKey.ReadTimeout,
getProperty(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT));
return config;
}
private Integer getProperty(IClientConfigKey<Integer> connectTimeout,
int defaultConnectTimeout) {
return environment.getProperty("ribbon." + connectTimeout, Integer.class,
defaultConnectTimeout);
}
@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;
}
}
PropertiesFactory
定义了五种核心组件的自定义参数,如想自定义某个client的负载均衡规则,则如下设置, testclient是服务名
testclient.ribbon.NFLoadBalanceRuleClassName=com.netflix.loadbalancer.RandomRule
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");
}
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 + "." + "ribbon"+ "." + classNameProperty);
return className;
}
return null;
}
}
RibbonClientSpecification
RibbonClient的自定义配置
在@RibbonClients和@RibbonClient中被引入
RibbonClientConfigurationRegistrar
将@RibbonClients和@RibbonClient中配置的服务名和@Configuration类封装为RibbonClientSpecification,注册到bean容器中
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
Map<String, Object> attrs = metadata
.getAnnotationAttributes(RibbonClients.class.getName(), true);
// @RibbonClients注解中配置的多个@RibbonClient注解
if (attrs != null && attrs.containsKey("value")) {
AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
for (AnnotationAttributes client : clients) {
registerClientConfiguration(registry, getClientName(client),
client.get("configuration"));
}
}
// @RibbonClients注解中如果有这个属性,表示这个配置类是全局的配置,所有RibbonClient公用的配置,name以default开头
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"));
}
// 注册单个@RibbonClient的配置类
Map<String, Object> client = metadata
.getAnnotationAttributes(RibbonClient.class.getName(), true);
String name = getClientName(client);
if (name != null) {
registerClientConfiguration(registry, name, client.get("configuration"));
}
}
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());
}
}
所以自定义Ribbon配置还可以如下操作
定义一个配置类,内部自定义组件,例如负载均衡策略
配置注解@RibbonClient,name是微服务名,就完成了自定义ribbon配置
但是要注意的是一点,自定义的配置类不能被启动类@ComponentScan扫描到,一旦扫描到,ribbon的默认配置中又都是@ConditionOnMissingBean,就变成了全局配置了。
还有官网说自定义配置类必须注解Configuration,但是我测试不注解也能生效