SpringBoot整合Ribbon源码分析之核心组件加载原理

前言:

在我的初次探讨中,我深入剖析了Ribbon的核心构建要素。这些要素涵盖了ILoadBalancer、IRule、IClientConfig、IPing、ServerList,以及ServerListUpdater。这一组件集合在协同互动中,共同致力于完善 Ribbon 的负载均衡机制,这一节我测试的代码都是Ribbon和Nacos的整合,使用Feign客户端调用然后一步步分析源码,这个文章不包括Feign的原理解析我会直接进入到Ribbon相关的源码里面进行分析。

源码分析:

在SpringBoot项目中,如果我们想要使用Ribbon的功能,需要引入自动配置类,它的依赖如下:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
  <version>2.2.9.RELEASE</version>
  <scope>compile</scope>
</dependency>

当我们引入这个依赖以后,会在spring.factories中有如下截图配置:

引入一个自动配置类RibbonAutoConfiguration,这个类的部分截图如下:

我们主要分析里面的RibbonClientSpecification、SpringClientFactory这两个类,它们是Ribbon的入口,现我一个个分析它们的作用。

1.RibbonClientSpecification

它的类定义信息如下:

package org.springframework.cloud.netflix.ribbon;

import java.util.Arrays;
import java.util.Objects;

import org.springframework.cloud.context.named.NamedContextFactory;

public class RibbonClientSpecification implements NamedContextFactory.Specification {

   private String name;

   private Class<?>[] configuration;

   public RibbonClientSpecification() {
   }

   public RibbonClientSpecification(String name, Class<?>[] configuration) {
      this.name = name;
      this.configuration = configuration;
   }

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public Class<?>[] getConfiguration() {
      return configuration;
   }

   public void setConfiguration(Class<?>[] configuration) {
      this.configuration = configuration;
   }

   @Override
   public boolean equals(Object o) {
      if (this == o) {
         return true;
      }
      if (o == null || getClass() != o.getClass()) {
         return false;
      }
      RibbonClientSpecification that = (RibbonClientSpecification) o;
      return Arrays.equals(configuration, that.configuration)
            && Objects.equals(name, that.name);
   }

   @Override
   public int hashCode() {
      return Objects.hash(configuration, name);
   }

   @Override
   public String toString() {
      return new StringBuilder("RibbonClientSpecification{").append("name='")
            .append(name).append("', ").append("configuration=")
            .append(Arrays.toString(configuration)).append("}").toString();
   }

}

可以看出它实现了Spring Cloud的
NamedContextFactory.Specification接口,NamedContextFactory这个类是允许创建一个Spring子容器,子容器里面的对象并不影响其他容器的对象,Specification接口可以配置一些特定的对象到一个子容器中。

这个类有个有参构造函数,分别是String类型的name,Class类型的数组configuration,configuration配置一些指定的对象加载到子容器中。

在Nacos的自动配置类中有个
NacosRibbonClientConfiguration类就是传入到RibbonClientSpecification的configuration属性,那么我们可以到RibbonAutoConfiguration的属性@Autowired(required = false) private List<RibbonClientSpecification> configurations = new ArrayList<>()中configurations就有值了,截图如下:

知道了这一点我们就可以继续往下分析SpringClientFactory

2.SpringClientFactory

该类的定义信息如下:

package org.springframework.cloud.netflix.ribbon;

import java.lang.reflect.Constructor;

import com.netflix.client.IClient;
import com.netflix.client.IClientConfigAware;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.ILoadBalancer;

import org.springframework.beans.BeanUtils;
import org.springframework.cloud.context.named.NamedContextFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class SpringClientFactory extends NamedContextFactory<RibbonClientSpecification> {

   static final String NAMESPACE = "ribbon";

   public SpringClientFactory() {
      super(RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name");
   }
   public <C extends IClient<?, ?>> C getClient(String name, Class<C> clientClass) {
      return getInstance(name, clientClass);
   }

   public ILoadBalancer getLoadBalancer(String name) {
      return getInstance(name, ILoadBalancer.class);
   }

   public IClientConfig getClientConfig(String name) {
      return getInstance(name, IClientConfig.class);
   }

   public RibbonLoadBalancerContext getLoadBalancerContext(String serviceId) {
      return getInstance(serviceId, RibbonLoadBalancerContext.class);
   }

   static <C> C instantiateWithConfig(Class<C> clazz, IClientConfig config) {
      return instantiateWithConfig(null, clazz, config);
   }

   static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
         Class<C> clazz, IClientConfig config) {
      C result = null;

      try {
         Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
         result = constructor.newInstance(config);
      }
      catch (Throwable e) {
         // Ignored
      }

      if (result == null) {
         result = BeanUtils.instantiate(clazz);

         if (result instanceof IClientConfigAware) {
            ((IClientConfigAware) result).initWithNiwsConfig(config);
         }

         if (context != null) {
            context.getAutowireCapableBeanFactory().autowireBean(result);
         }
      }

      return result;
   }

   @Override
   public <C> C getInstance(String name, Class<C> type) {
      C instance = super.getInstance(name, type);
      if (instance != null) {
         return instance;
      }
      IClientConfig config = getInstance(name, IClientConfig.class);
      return instantiateWithConfig(getContext(name), type, config);
   }

   @Override
   protected AnnotationConfigApplicationContext getContext(String name) {
      return super.getContext(name);
   }

}

可以看出它实现了NamedContextFactory接口,并指定泛型参数RibbonClientSpecification,它是Spring一个子容器对象,在子容器里面创建的对象互不影响,它调用了父类的构造方法,传入
RibbonClientConfiguration.class, NAMESPACE, "ribbon.client.name",分别赋值给父类的Class<?> defaultConfigType属性、 final String propertySourceName属性、final String propertyName属性,接着调用SpringClientFactory的setConfigurations方法传入List<RibbonClientSpecification>集合,我们看看这个类的方法:

public void setConfigurations(List<C> configurations) {
   for (C client : configurations) {
      this.configurations.put(client.getName(), client);
   }
}

遍历configurations集合,往父类configurations的数据结构是Map<String, C>中放入key是名称,value是RibbonClientConfiguration对象。

到此RibbonClientSpecification、SpringClientFactory的初始化就完成了,下面我们看看调用过程都发生了什么。

我的测试类是通过一个Feign进行远程调用,调用TestFeign类的testFeign方法,我的方法截图如下:

TestFeign的testFeign方法

调用栈如下图所示:

这里可以看到TestFeign最终会调用到SpringClientFactory的getClientConfig方法,最终会调用到SpringClientFactory的createContext方法,调用链的关系是这样的:

SpringClientFactory.getClientConfig(String name) ->

SpringClientFactory.getInstance(String name,Class Type)->

SpringClientFactory.getContext(String name)->

父类
NamedContextFacotry.createContext(String name)

关键代码的就在SpringClientFactory父类的NamedContextFacotry的createContext方法,该方法的代码定义如下:

protected AnnotationConfigApplicationContext createContext(String name) {
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   if (this.configurations.containsKey(name)) {
      for (Class<?> configuration : this.configurations.get(name)
            .getConfiguration()) {
         context.register(configuration);
      }
   }
   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)));
   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;
}

这个方法手动创建了一个
AnnotationConfigApplicationContext容器对象,判断SpringClientFactory的成员属性configurations是否包含请求的名称,我这个例子名称是hn-mansion-adapter,很显然我们之前添加的configurations不包含这个名称,接着向下执行,获取configurations的Map.entry集合进行遍历,判断key是否是包含default.,这个是符合的,取出对象RibbonClientConfiguration的getConfiguration属性。

这个属性是我们前面Nacos的
NacosRibbonClientConfiguration,然后调用AnnotationConfigApplicationContext对象的register方法,从这个方法的定义可以看出来是往容器中注册NacosRibbonClientConfiguration的Class类,又往容器注册PropertyPlaceholderAutoConfiguration的Class类,和defaultConfigType,这个defaultConfigType是什么那?

在前面分析过是RibbonClientConfiguration,然后调用容器的刷新方法进行对象的创建。

从上面分析可以得出的结论是如果有我们的自动配置类比如是
NacosRibbonClientConfiguration会先加载里面的Bean,如果没有会加载RibbonClientConfiguration中的Bean,而RibbonClientConfiguration中的Bean都是@ConditionalOnMissingBean标注的,这表明如果容器中存在这个Bean就不会加载这个Bean,以我们的NacosRibbonClientConfiguration中配置的Bean为最优先加载的。

所以
NacosRibbonClientConfiguration中定义了一个ServerList的Bean会优先于RibbonClientConfiguration的ServerList的Bean,所以最终加载到容器中的ServerList是Nacos提供的NacosServerList。

但是其他组件如IRule在
NacosRibbonClientConfiguration没有定义所以会加载RibbonClientConfiguration的Bean,知道了这些核心组件的加载原理,下一篇文章我会分析组件的调用过程。

总结:

这篇文章分析了Ribbon核心组件的加载过程,通过源码分析可以得知是在Ribbon客户端自己创建了一个Spring子容器可以定制化Bean的创建,比如我想定义自己的负载均衡算法、以及服务实例列表的来源,通过这种源码的设计可以使得不用的负载均衡客户端有不同的算法互相并不影响。好了,这篇文章就分析到这里,下一节我将分析负载均衡的调用过程,如果喜欢我的文章请点赞关注。

  • 29
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值