隨著微服務(wù)的項目越來越多标沪,對Ribbon的使用也逐漸增多榄攀,之前小編在項目中也只是簡單的了解Ribbon的作用,對于Ribbon是如何進(jìn)行負(fù)載的原理也不全面金句,所以在此進(jìn)行分析和總結(jié)檩赢,在后續(xù)也會分析和總結(jié)Spring Boot2.4.x版本之后默認(rèn)的負(fù)載均衡Spring Cloud LoadBalance!
RibbonAutoConfiguration配置文件
作為Ribbon的入口配置文件违寞,通過spring.factories進(jìn)行加載
@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),但是為什么要通過這樣動態(tài)方式去注冊配置類呢军浆?仔細(xì)想想也不難理解,因為需要根據(jù)用戶的配置(@RibbonClients挡闰、@RibbonClient)進(jìn)行注冊實例
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都是通過該工廠
* 創(chuàng)建客戶端、負(fù)載平衡器和客戶端配置實例的工廠
* 為每個客戶端名稱創(chuàng)建一個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);
}
......
這樣的解釋感覺有點牽強(qiáng)摄悯,我們繼續(xù)分析下赞季,SpringClientFactory繼承NamedContextFactory,這個NamedContextFactory我們下文會繼續(xù)講解奢驯,我們把焦點關(guān)注到構(gòu)造函數(shù)申钩,3個參數(shù),分別是配置類瘪阁、命名空間撒遣、屬性字段名,通過構(gòu)造函數(shù)管跺,我們就將RibbonClientConfiguration配置類引入進(jìn)來了义黎,簡單理解就是RibbonClientConfiguration這個配置類被Spring掃描到了,該配置類下面的@Bean配置在后續(xù)就會通過懶加載方式進(jìn)行訪問伙菜,最后一個這個屬性名會在后續(xù)的分析中提到
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獲取對應(yīng)的實例時候火的,我們可以看到為每一個客戶端名稱(name屬性)都會創(chuàng)建一個Spring ApplicationContext,然后這個客戶端上下文就可以獲取Spring容器中的Bean實例,比如當(dāng)前業(yè)務(wù)中我的聚合服務(wù)A訪問領(lǐng)域服務(wù)B淑倾,通過@FeignClient方式進(jìn)行訪問馏鹤,那么此時getInstance中的name就是@FeignClient注解中的name或者value值,this.propertyName()這個參數(shù)對應(yīng)的值就為@FeignClient注解中的name或者value值
LoadBalancerClient
Spring Cloud 對外提供的負(fù)載均衡客戶端接口娇哆,默認(rèn)實現(xiàn)類也能看出使用的是RibbonLoadBalancerClient,那這個類是怎么用起來的呢湃累?通過什么方式才能被用起來勃救?我們放到Spring Cloud Ribbon 分析(二)之LoadBalancerAutoConfiguration進(jìn)行總結(jié)!
入口配置類比較重要的幾個分析總結(jié)告一段落!