Spring Cloud Ribbon 分析(一)之Ribbon
随着微服务的项目越来越多,对Ribbon的使用也逐渐增多,之前小编在项目中也只是简单的了解Ribbon的作用,对于Ribbon是如何进行负载的原理也不全面,所以在此进行分析和总结,在后续也会分析和总结Spring Boot2.4.x版本之后默认的负载均衡Spring Cloud LoadBalance!
RibbonAutoConfiguration配置文件
作为Ribbon的入口配置文件,通过spring.factories进行加载
@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, ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
@Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
@Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
@Bean
@ConditionalOnMissingBean(LoadBalancerClient.class)
public LoadBalancerClient loadBalancerClient() {
return new RibbonLoadBalancerClient(springClientFactory());
}
......
}
以上为配置文件比较重要的几个点,下文会逐一分析具体作用
@RibbonClients注解
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
@Documented
@Import(RibbonClientConfigurationRegistrar.class)
public @interface RibbonClients {
RibbonClient[] value() default {};
Class<?>[] defaultConfiguration() default {};
}
@Import(RibbonClientConfigurationRegistrar.class),字面意思不难理解,就是将@RibbonClients注解中配置的value与defaultConfiguration配置类注册到Spring容器中(实例RibbonClientSpecification),但是为什么要通过这样动态方式去注册配置类呢?仔细想想也不难理解,因为需要根据用户的配置(@RibbonClients、@RibbonClient)进行注册实例
RibbonClientConfigurationRegistrar
public class RibbonClientConfigurationRegistrar implements ImportBeanDefinitionRegistrar {
@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());
}
}
通过代码片段得知@RibbonClients注解配置会生成RibbonClientSpecification实例
SpringClientFactory
/**
* 非常重要的一个工厂类,Ribbon的很多获取实例Bean都是通过该工厂
* 创建客户端、负载平衡器和客户端配置实例的工厂
* 为每个客户端名称创建一个Spring ApplicationContext,可以从中获取需要的bean
*/
public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {
static final String NAMESPACE = "ribbon";
public SpringClientFactory() {
super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
}
......
public ILoadBalancer getLoadBalancer(String name) {
return getInstance(name, ILoadBalancer.class);
}
public IClientConfig getClientConfig(String name) {
return getInstance(name, IClientConfig.class);
}
......
这样的解释感觉有点牵强,我们继续分析下,SpringClientFactory继承NamedContextFactory,这个NamedContextFactory我们下文会继续讲解,我们把焦点关注到构造函数,3个参数,分别是配置类、命名空间、属性字段名,通过构造函数,我们就将RibbonClientConfiguration配置类引入进来了,简单理解就是RibbonClientConfiguration这个配置类被Spring扫描到了,该配置类下面的@Bean配置在后续就会通过懒加载方式进行访问,最后一个这个属性名会在后续的分析中提到
NamedContextFactory
上文讲到SpringClientFactory继承NamedContextFactory,这是一个用命名空间划分的一个上下文工厂类,那这个类具体有什么作用呢,我们接着讲
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification>
implements DisposableBean, ApplicationContextAware {
public interface Specification {
String getName();
Class<?>[] getConfiguration();
}
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
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) {
......
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.setDisplayName(generateDisplayName(name));
context.refresh();
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;
}
......
}
SpringClientFactory.getInstance获取对应的实例时候,我们可以看到为每一个客户端名称(name属性)都会创建一个Spring ApplicationContext,然后这个客户端上下文就可以获取Spring容器中的Bean实例,比如当前业务中我的聚合服务A访问领域服务B,通过@FeignClient方式进行访问,那么此时getInstance中的name就是@FeignClient注解中的name或者value值,this.propertyName()这个参数对应的值就为@FeignClient注解中的name或者value值
LoadBalancerClient
Spring Cloud 对外提供的负载均衡客户端接口,默认实现类也能看出使用的是RibbonLoadBalancerClient,那这个类是怎么用起来的呢?通过什么方式才能被用起来?我们放到Spring Cloud Ribbon 分析(二)之LoadBalancerAutoConfiguration进行总结!
入口配置类比较重要的几个分析总结告一段落!